Fórum Root.cz
Hlavní témata => Vývoj => Téma založeno: unixuser 01. 06. 2025, 14:06:54
-
Ahoj, moja otázka je veľmi jednoduchá. Je jazyk C skutočne ťažký alebo to je iba môj osobný dojem?
-
Já myslím, že ty dva (https://www.kopp.cz/607-ucebnice-jazyka-c) díly (https://www.kopp.cz/542-ucebnice-jazyka-c-2-dil) dohromady nevážej ani dvě kila.
-
Jazyk C jako takový je relativně jednoduchý, i pokud zohledníme standardní knihovny. Je relativně blízko k železu a k operačnímu systému (tady na root.cz předpokládám hlavně POSIX systémy). To má plusy i mínusy.
Otázka byla asi ale, jestli je v C těžké psát? Tam bych to viděl hlavně tak, že není každý jazyk vhodný na vše a některé věci opravdu není snadné v C napsat a pokud neoceníme plusy C v daném případě, tak nejspíš bude lepší zvolit na to jiný jazyk. Klasický příklad je pitomá práce s textovými řetězci, ta se v mnoha jiných jazycích píše mnohem jednodušeji (korektně, bez různých overflow apod. v okrajových situacích).
-
Špecifikujem, myslel som konkrétne či je ťažký na naučenie... mne príde náročnejší než taký python.
-
C je v základu jednoduchý. Nemá žádné věci jako výjimky, generátory (yield), objekty a třídy, má jen minimum datových typů, atd. Ale všechno složitější si tam musíš poskládat od základů, nebo se zamotat do hromady toolkitů a knihoven, a nechá tě střelit se do vlastní nohy, takže pokud je pro tebe jednodušší slepovat dohromady nějaké černé krabičky bez chápání, co asi dělají a proč, no, pak je Cčko složitější. Tak, jako je složitější Ford model T, ve kterém bude asi tak 5 metrů drátů, s žádnou elektronikou, a pár táhly a šoupátky, než dnešní auta s kilometry drátů, hromadou elektroniky, variabilním časováním...
Edit: Pokud ale chceš dělat něco víc, než si slepit pár malých pokusů, tak stejně je vhodné vyzkoušet i to Cčko pro pochopení, jak spousta automagických věcí vlastně funguje. Nebo si pro Pythonní program napsat výkonově kritickou část v Cčku.
-
C je zlozitejsi do hlbky, treba pri nom viac rozmyslat, do hlbky rozumiet problemu, niekedy rozumiet aj hardwaru. Python je zlozitejsi do sirky, ma zlozitejsi syntax, treba si pri nom pamatat viac veci. Kazdy z nich je vdhodnejsi na nieco ine. Cecko je vhodnejsie na low level veci, optimalizovane na rychlost, el. spotrebu, usporu RAM. Python je vhodnejsi na tu vyssiu vrstvu, webovy backend, REST API. Aj vdaka jeho frameworkom (Django, FastApi, Flask...), ale doslova nevhodny na iterovanie celkeho mnozstva da. Vela python modulov su napisane v cecku, hlavne veci okolo spracovania dat. Idealne vediet obidva, aj ako "integrovat" cecko do pythonu (npr cpython).
-
Rozdělil bych to na dvě otázky:
Je C těžký jazyk? NE
Je těžké v C programovat? ANO
C je jednoduchý jazyk, který se naučíš za chvíli. Důsledkem této jednoduchosti je to, že tvoje programy budou složitější – nedá se to obejít, existuje nějaká inherentní složitost programu, který se snažíš napsat, a čím méně toho za tebe řeší programovací jazyk (nebo knihovny), tím více toho musíš ve svém kódu řešit ty.
Tím nechci na C házet špínu. Ten jazyk má velký přínos – je potřeba to vidět v kontextu, ve kterém vznikal. Jednak umožňuje přenositelnost mezi platformami a jednak strukturované a mnohem přehlednější programování oproti assembleru. A dodnes má svůj smysl pro psaní nízkoúrovňových věcí. Taky slouží jako de facto standardní rozhraní mezi nativními knihovnami a programy psanými ve vyšších programovacích jazycích.
Určitě je dobré C umět. Ale pro psaní programů/aplikací bych ho nedoporučoval. Pokud budeš psát jádro systému nebo základní systémové nástroje, tak ano, tam to má smysl i z důvodu „bootstrappingu“ protože kompilátor C je jednoduchý a máš tak minimum závislostí k tomu, abys postavil základ systému. Ale pro další aplikace bych použil aspoň C++ nebo D, Rust, Go, Javu…
-
Asi uz chapu otazku. Zvladnul jsi python a ted se ohlizis po C/C++.
Presel jsem Pascal, C, C++, C sharp, Python. Jestli umis C pak prejit na python je lehke. Mnohe veci zjednodusuje. Zbavuje te prace s pameti malloc/free, chrani ti pole (overflow), automaticky inicializuje promenne a pole. Na mnoho veci uz jsou napsane funkce primo v zakladu (string, pole, list).
A to je vlastne i odpoved na tvou otazku. Pri prechodu z python na C se tohle vsechno musis naucit, vedet a programovat.
Jestli je tvuj prvni jazyk python pak to pro tebe bude tezke ale rozhodne to nevzdavej.
-
Ahoj, moja otázka je veľmi jednoduchá. Je jazyk C skutočne ťažký alebo to je iba môj osobný dojem?
Dnes, tedy v dome, kdy mame -Werror -Wall -Wextra, kdy kompiler dokaze prijit na drtivou vetsinu beznych chyb sam, a soucasne v dobe, kdy jedina chyba ve vysledku nasi praci neznamena katastrofu (coz, v pripade nekterych platforem znamenalo pad celeho serveru a nasledny reboot - kdo z mistnich si jeste pamatuje "ABEND - blabla.NLM did not freed 58 bytes of Allocated Short Term Memory, type EXIT to reboot" kdyz clovek zapomel na par bajtu?), tak ano, naucit se delat v Cecku beru jako hodne jednoduche.
Naucit se delat v cecku dobre je o dost tezsi a znamena to alespon trochu nahlednout i do assembleru, ale tam cloveka casto donuti sam zivot (jako IGBT nejsou uplne levne, a kdyz se odporouci za silvestrovskych efektu pate v rade do kremikoveho nebe spolu s usmernovacem na vstupu, protoze programator udelal trivialni chybu v programu, kterym to ovlada, tak uz si da pozor kazdy)
-
Důsledkem této jednoduchosti je to, že tvoje programy budou složitější
Moje zkušenost je jiná. Přepisoval jsem pár programů z F# do C a byly jednodušší. Jeden z důvodů byl, že jsem nahradil složitější algoritmus jednodušším - mohl jsem si to dovolit, protože C je rychlejší.
A přešel jsem právě z důvodů jednoduchosti, protože optimalizovat kód v F# pod určitou hranici bylo těžší než v C (v F# jsem hodně řešil, jak se vyhnout alokacím a tím i spouštění GC, což tenhle problém v C není).
-
Jazyk ako taky nie je tazky, len vsetko si tam musis osefovat sam. Na jednej strane je to fajn, lebo mas nad vsetkym kontrolu, ale na druhej strane je to zas nevyhoda, lebo to stoji cas (a cas su peniaze). Preto aj vznikli vyssie jazyky, ktore ta vlastne odbremenia od vela takychto veci a ty sa viac mozes sustredit na samotny program.
-
Je jazyk C skutočne ťažký
C je jednoduchý jazyk, který se naučíš za jedno odpoledne.
-
... má jen minimum datových typů ...
Pokud se týče základních datových typů, tak bych naopak řekl, že jako snad jediný jazyk (kromě assembleru) poskytuje kompletní množinu různě dlouhých integerů se znaménkem nebo bez (a floaty a pointery). Sice se integer typy divně jmenují (char, short, long, long long) takže si je lidi někdy pro lepší přehlednost přejmenovávají na něco jako u16, nebo s32... ale těch pár jiných jazyků, které, jsem potkal, mě většinou tlačilo nějak do kouta stylem "tumáš nějaký integer a neotravuj s detailismem". Na to navazují bit-banging operace AND, OR, XOR, NOT.
Žádné rozmazlování garbage collectorem - máte lokální proměnné na stacku a globální tuším na heapu, a na případnou dynamickou alokaci z heapu máte v případě zájmu explicitní malloc() a free().
Jazyk je to závorkovatý, takže se nemusíte bát, že se Vám program rozbije změnou odsazení řádku.
Zároveň není tak ukecaný jako někteří jeho vrstevnící, kteří uzavírají blok mezi slůvka BEGIN a END :-)
Ten jazyk má přímý přístup k systémovým standardním knihovnám a syscallům. Vyšší jazyky toto C-level API musí nějak "balit" pro své potřeby, mohou být pozadu / nabízet jenom podmnožinu, nabalit další vrstvu abstrakce která může věci zjednodušovat a ve složitějších případech překáží apod.
Jsou situace / zadání, kde je zcela legitimní a vhodné, použít něco jiného než právě C :-)
Je to osobní volba. Mě v hadích letech vždycky zajímalo, jak věci fungují "pod kapotou", a když jsem po setkání s Basicem a Pascalem potkal vlastním přičiněním nakonec i Céčko, byla to pro mě veliká úleva. Že nemusím prosté věci řešit divnými oklikami, zároveň ale nemusím zabředat do detailů instrukční sady procesoru.
-
Na C je tezka k pochopeni a pouziti jedina vec. Uvedomit si co je pointer, jaky vztah to ma k poli a jake dusledky ve vztahu k pameti.
Kdyz clovek pochopi ten princip, je to pruzracne jasne (coz neznamena, ze jednoduche na uhlidani). Ale zkusenost me rika, ze to pro lidi odchovane v Pythonu je tezky krok na pochopeni. A prijde me to zvlastni, protoze clovek nemuze byt dobry programator, dokud nema predstavu jak pocitac pracuje s pameti a jake ma dusledky co naprogramuje.
PS: druha prekvapive nepochochopena vec jsou ciselne soustavy. Pritom je oboje v principu uplne snadne.
-
Nie nie je. Noapak, je to jeden s tych jazykov, pri ktorom clovek vie co robi, lebo je to tam vzdy explicitne napisane.
-
Ale zkusenost me rika, ze to pro lidi odchovane v Pythonu je tezky krok na pochopeni. A prijde me to zvlastni, protoze clovek nemuze byt dobry programator, dokud nema predstavu jak pocitac pracuje s pameti a jake ma dusledky co naprogramuje.
Bingo. Za me by clovek mel zacit nekde nizko, i kdyz v budoucnu planuje pracovat v nejakem vyssim jazyku, presne ze zminenych duvodu - kdyz uz jednou clovek vi, co je dole, tak mnohem lepe vyuzije vyhod vyssiho jazyka, ktery spoustu prace udela za nej (prestoze ta prace tam porad je, jen skryta, a ve vysledku se na vypocetni slozitosti muze nehezky projevit).
...tezko na cvicisti, lehko na bojisti
Jen co mi na Cecku vzdycky prudilo (nevim, jestli to nejak novejsi norma uz neresi, na veci, co delam ted jsem to uz nepotreboval), byla/je nekompletnost bitovych operaci, kdyz si clovek zvykne na bitove rotace bez/s Carry flagem, resp xchg, tak vyjadrit to prenostielne v cecku je drbani se levou zadni za uchem (pripadne v makru), spojene s virou v prekladac, ze pri optimalizaci z toho sloziteho vyrazu pochopi, ktere 2 instrukce to ma vygenerovat.
-
Jen co mi na Cecku vzdycky prudilo (nevim, jestli to nejak novejsi norma uz neresi, na veci, co delam ted jsem to uz nepotreboval), byla/je nekompletnost bitovych operaci, kdyz si clovek zvykne na bitove rotace bez/s Carry flagem, resp xchg, tak vyjadrit to prenostielne v cecku je drbani se levou zadni za uchem (pripadne v makru), spojene s virou v prekladac, ze pri optimalizaci z toho sloziteho vyrazu pochopi, ktere 2 instrukce to ma vygenerovat.
Right shift je v C minové pole. Standard říká, že right shift na signed integeru je implementation defined, což znamená, že se překladač může rozhodnout, jestli vygeneruje logical nebo aritmetic right shift. Jaké to má důsledky, je celkem jasné, může to dávat různé výsledky na různých platformách (dokonce i různé výsledky na stejné platformě, pokud se překladač rozhodne udělat nějakou optimalizaci). Opravené je to až v C++20, kde je jasně definované, že je to arithmetic right shift. Nevím o tom, že by to bylo opravené v nějaké C normě, tam je to AFAIK pořád implementation defined.
-
Ani ne tak těžký, jako čím dál víc zbytečný.
-
... má jen minimum datových typů ...
Pokud se týče základních datových typů, tak bych naopak řekl, že jako snad jediný jazyk (kromě assembleru) poskytuje kompletní množinu různě dlouhých integerů se znaménkem nebo bez (a floaty a pointery). Sice se integer typy divně jmenují (char, short, long, long long) takže si je lidi někdy pro lepší přehlednost přejmenovávají na něco jako u16, nebo s32... ale těch pár jiných jazyků, které, jsem potkal, mě většinou tlačilo nějak do kouta stylem "tumáš nějaký integer a neotravuj s detailismem". Na to navazují bit-banging operace AND, OR, XOR, NOT.
Nepojmenovavaji.. jsou jiz pojmenovana - vhodnym standardem - ktery zaruci ze na vsech platformach se to bude chovat stejne.
#include <stdint.h>
https://pubs.opengroup.org/onlinepubs/009695399/basedefs/stdint.h.html
Jako druhy problem ktery clovek pak ma - je udelat prenositelny printf() formatovaci retezec :)
-
Na nějaké halucinace v chování shiftu jsem narazil v implementaci CRC v modbusové knihovně (mé vlastní). To CRC jsem si někde "půjčil" - a přestože GCC na x86 mi to dlouhá léta chroupalo správně, tak GCC na ARMu dávalo nesprávný součet. Přisuzoval jsem to nějakému rozdílu v inicializaci defaultních hodnot bitů v registrech mezi platformami nebo co... v zásadě mi stačilo ty operace pečlivěji (explicitněji) uzávorkovat a otypovat, a začalo se to chovat na obou platformách stejně...
Jojo, printf... různé verze a varianty format stringu se navzájem liší v drobných nuancích...
Na druhou stranu jsem zaznamenal, že jak C++ zamlada prudilo "všichni musí používat iostream a jeho C++ manipulátory, na printf() zapomeňte", tak později tenhle fundamentalismus trochu vyvanul a printf() se dále používá jako legitimní funkce pro vkládání proměnných do textového řetězce...
-
Ani ne tak těžký, jako čím dál víc zbytečný.
Zatial neviem o jazyku, ktory by ho nahradil.
-
Ccko, narozdil od mnoha vsemoznych frikulinksych kravovin, po kterych za par let pes nestekne, dela presne to, co napises. Takze se ti nestane, ze napises radek a vygeneruje se z toho GB binarky.
-
Ccko, narozdil od mnoha vsemoznych frikulinksych kravovin, po kterych za par let pes nestekne, dela presne to, co napises. Takze se ti nestane, ze napises radek a vygeneruje se z toho GB binarky.
ja mam taky rad c, ale pro rychlejsi vyvoj pouzivam go, ktery povazuju za jeho potomka.
v praci jsem nucen pouzivat c++, ktere mi prijde zase silene rozsahle a jeho potomek je rust, se kterym si taky hraju, ale nepripada mi takovy pohodovy jako go.
-
Ani ne tak těžký, jako čím dál víc zbytečný.
Zatial neviem o jazyku, ktory by ho nahradil.
Vždy a všude? Zatím možná není. Ale že čím dál víc projektů, které nahrazují původní věci psané v C. A je tu Zig a někdo nedávno vzpomínal nějaké C3 - to by mohly být přímé náhrady, když už někomu nesedí třeba Rust ani C++. Že C bude čím dál víc legacy, považuju za nevyhnutelné.
-
Ccko, narozdil od mnoha vsemoznych frikulinksych kravovin, po kterych za par let pes nestekne, dela presne to, co napises.
Zkus si domácí úkol, když napíšu tohle, bude výsledek -1, nebo 2147483647, nebo ještě něco úplně jiného?
printf("%d\n", -1 >> 1);
-
Zkus si domácí úkol, když napíšu tohle, bude výsledek -1, nebo 2147483647, nebo ještě něco úplně jiného?
printf("%d\n", -1 >> 1);
Tohle je prasana. Nikdo kdo ma praxi v C by tohle nenapsal. Spravne je to
printf("%d\n", ((int)-1) >> 1);
-
Zkus si domácí úkol, když napíšu tohle, bude výsledek -1, nebo 2147483647, nebo ještě něco úplně jiného?
printf("%d\n", -1 >> 1);
Tohle je prasana. Nikdo kdo ma praxi v C by tohle nenapsal. Spravne je to
printf("%d\n", ((int)-1) >> 1);
nesmysl, obe dve verze jsou naprosto totozne protoze -1
a ((int) -1)
je jedno a uplne to same. V C je numericky literal by default typu int.
C zachovava pri rotaci znamenko protoze rotace je provadena na znamenkovem typu, dela tedy tzv. aritmeticky bit shift.
Pokud by clovek chtel klasicky bit shift musi naopak prevest hodnotu na unsigned integer kde se nepracuje se znamenkem - a provest rotaci na nem.
printf("%d\n", -1 >> 1);
printf("%u\n", ((unsigned)-1) >> 1);
A taky zkuseny C programator by nepouzil -1 ale bud konstantu z <limits.h> a nebo ~0
, ponevadz -1
dava same jednicky jen na architekturach kde jsou zaporna cisla implementovana pomoci dvojkoveho doplnku, coz nemusi byt vsude. Existuji architektury, ktere zaporna cisla implementuji jinak.
~0
funguje vzdy a vsude.
Takze C dela presne to co ma a jestli nekdo tvrdi neco jineho, je to pouze jeho neznalost.
-
Ccko, narozdil od mnoha vsemoznych frikulinksych kravovin, po kterych za par let pes nestekne, dela presne to, co napises.
Zkus si domácí úkol, když napíšu tohle, bude výsledek -1, nebo 2147483647, nebo ještě něco úplně jiného?
printf("%d\n", -1 >> 1);
Podle specifikace C23 bude výsledek záviset na implementaci (část 6.5.8):
The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a
signed type and a nonnegative value, the value of the result is the integral part of the quotient of
E1/2E2 . If E1 has a signed type and a negative value, the resulting value is implementation-defined
Nicméně u většiny ostatních jazyků to vlastně ani nevíte, protože žádnou specifikaci nemají.
-
Jako druhy problem ktery clovek pak ma - je udelat prenositelny printf() formatovaci retezec :)
Jestli nekdo nezna, tak to tu poslu, je na to header <inttypes.h>
#include <stdio.h>
#include <inttypes.h>
int main()
{
int8_t var_int8 = -42;
uint8_t var_uint8 = 42;
int16_t var_int16 = -1600;
uint16_t var_uint16 = 1600;
int32_t var_int32 = -32000;
uint32_t var_uint32 = 32000;
int64_t var_int64 = -123456789012345;
uint64_t var_uint64 = 123456789012345;
printf("var_int8 : %" PRId8 "\n", var_int8);
printf("var_uint8 : %" PRIu8 "\n", var_uint8);
printf("var_int16 : %" PRId16 "\n", var_int16);
printf("var_uint16: %" PRIu16 "\n", var_uint16);
printf("var_int32 : %" PRId32 "\n", var_int32);
printf("var_uint32: %" PRIu32 "\n", var_uint32);
printf("var_int64 : %" PRId64 "\n", var_int64);
printf("var_uint64: %" PRIu64 "\n", var_uint64);
return 0;
}
https://pubs.opengroup.org/onlinepubs/009695399/basedefs/inttypes.h.html (https://pubs.opengroup.org/onlinepubs/009695399/basedefs/inttypes.h.html)
-
Ahoj, moja otázka je veľmi jednoduchá. Je jazyk C skutočne ťažký alebo to je iba môj osobný dojem?
C je jednoduchoučký jazyk, ve kterém je zatraceně těžké programovat ;)
Je jednoduchý, protože je to jazyk z kompilátorové doby kamenné. To, co umí a co tedy musí umět překladač je osekané na naprosté minimum. Proto má každý krumpl překladač Cčka. Napsat základní překladač je fakt jednoduché.
Z toho plyne první peklo. Překladač neudělá skoro nic. Všechno je na tobě jako programátorovi. Včetně tupé mechanické dělničiny, kterou my lidi umíme podstatně hůř než stroje. Takže nám to dýl trvá, zato tam nasekáme kopec zbytečných chyb. ;)
Druhé peklo je v tom, že i to co umí je plné (z dnešního pohledu) zbytečných komplikací a pastí. Jeden příklad za všechny:
- Jako programátor máš za úkol zajisti aby ti sečtení intů nikdy nepřeteklo. Jinak je to undefined behavior a můžou se dít fakt divné věci. Už jsi někdy krokoval časoprostorovou anomálii?
- Dostaneš nulovou podporu pro to, abys to zajistil. Jazyk C je high level assembler, ve kterém není add with carry. Schválně si to zkuste ve standardním C napsat. Ano, asi tak všechny překladače pro to mají nějakou intrinsiku, páč se bez toho funguje fakt blbě.
Z toho plyne jeden zajímavý praktický důsledek. Nikdo nepíše v C. Vždycky je to nějaký dialekt, protože bez nadstandardních rozšíření se v tom jazyce nedá fungovat. A samozřejmě, že mezi těmi dialekty jsou občas dost zásadní rozdíly.
-
C zachovava pri rotaci znamenko protoze rotace je provadena na znamenkovem typu, dela tedy tzv. aritmeticky bit shift.
Ani náhodou. Right shift na signed integeru je v C implementation defined. Překladač to může přeložit jako arimetický, nebo jako logický shift. Výsledek bude samozřejmě úplně jiný. Ke zbytku toho, co jsi napsal, se nebudu vyjadřovat, je to stejně nesprávné jako tvoje tvrzení, že je to aritmetický bit shift.
-
Zkus si domácí úkol, když napíšu tohle, bude výsledek -1, nebo 2147483647, nebo ještě něco úplně jiného?
printf("%d\n", -1 >> 1);
Tohle je prasana. Nikdo kdo ma praxi v C by tohle nenapsal. Spravne je to
printf("%d\n", ((int)-1) >> 1);
nesmysl, obe dve verze jsou naprosto totozne protoze -1
a ((int) -1)
je jedno a uplne to same. V C je numericky literal by default typu int.
C zachovava pri rotaci znamenko protoze rotace je provadena na znamenkovem typu, dela tedy tzv. aritmeticky bit shift.
Pokud by clovek chtel klasicky bit shift musi naopak prevest hodnotu na unsigned integer kde se nepracuje se znamenkem - a provest rotaci na nem.
printf("%d\n", -1 >> 1);
printf("%u\n", ((unsigned)-1) >> 1);
A taky zkuseny C programator by nepouzil -1 ale bud konstantu z <limits.h> a nebo ~0
, ponevadz -1
dava same jednicky jen na architekturach kde jsou zaporna cisla implementovana pomoci dvojkoveho doplnku, coz nemusi byt vsude. Existuji architektury, ktere zaporna cisla implementuji jinak.
~0
funguje vzdy a vsude.
Takze C dela presne to co ma a jestli nekdo tvrdi neco jineho, je to pouze jeho neznalost.
Pravda. Spravne se to ma napsat.
printf("%u\n", ((uint16_t)-1) >> 1);
-
Nicméně u většiny ostatních jazyků to vlastně ani nevíte, protože žádnou specifikaci nemají.
Nechtěl jsem to vytahovat, ale v Rustu je to definované docela jasně :) https://doc.rust-lang.org/reference/expressions/operator-expr.html
Arithmetic right shift on signed integer types, logical right shift on unsigned integer types.
-
Ahoj, moja otázka je veľmi jednoduchá. Je jazyk C skutočne ťažký alebo to je iba môj osobný dojem?
C je jednoduchoučký jazyk, ve kterém je zatraceně těžké programovat ;)
Je jednoduchý, protože je to jazyk z kompilátorové doby kamenné. To, co umí a co tedy musí umět překladač je osekané na naprosté minimum. Proto má každý krumpl překladač Cčka. Napsat základní překladač je fakt jednoduché.
Z toho plyne první peklo. Překladač neudělá skoro nic. Všechno je na tobě jako programátorovi. Včetně tupé mechanické dělničiny, kterou my lidi umíme podstatně hůř než stroje. Takže nám to dýl trvá, zato tam nasekáme kopec zbytečných chyb. ;)
Druhé peklo je v tom, že i to co umí je plné (z dnešního pohledu) zbytečných komplikací a pastí. Jeden příklad za všechny:
- Jako programátor máš za úkol zajisti aby ti sečtení intů nikdy nepřeteklo. Jinak je to undefined behavior a můžou se dít fakt divné věci. Už jsi někdy krokoval časoprostorovou anomálii?
- Dostaneš nulovou podporu pro to, abys to zajistil. Jazyk C je high level assembler, ve kterém není add with carry. Schválně si to zkuste ve standardním C napsat. Ano, asi tak všechny překladače pro to mají nějakou intrinsiku, páč se bez toho funguje fakt blbě.
Z toho plyne jeden zajímavý praktický důsledek. Nikdo nepíše v C. Vždycky je to nějaký dialekt, protože bez nadstandardních rozšíření se v tom jazyce nedá fungovat. A samozřejmě, že mezi těmi dialekty jsou občas dost zásadní rozdíly.
Ono je zakazano pouzivat knihovny?
Aritmetiku s velkymi cisly resi gmp knihovna. Proc znovuvynalezat kolo?
https://gmplib.org/ (https://gmplib.org/)
Tak treba lidi co delaji linux v C pisou.
Ja v C taky pisu a je to muj nejoblibenejsi jazyk.
-
Pravda. Spravne se to ma napsat.
printf("%u\n", ((uint16_t)-1) >> 1);
spravne (a bez specifikace bitove sirky) takto:
~0u >> 1
-
Ahoj, moja otázka je veľmi jednoduchá. Je jazyk C skutočne ťažký alebo to je iba môj osobný dojem?
- Dostaneš nulovou podporu pro to, abys to zajistil. Jazyk C je high level assembler, ve kterém není add with carry. Schválně si to zkuste ve standardním C napsat. Ano, asi tak všechny překladače pro to mají nějakou intrinsiku, páč se bez toho funguje fakt blbě.
Ve standardní knihovně v stdckdint.h je ckd_add
-
Tak treba lidi co delaji linux v C pisou.
Ja v C taky pisu a je to muj nejoblibenejsi jazyk.
Linux není psaný v C ale v GCC dialektu. Portování do clangu se provedlo tak, že do clangu přidali nezbytnou část gcc dialektu ;)
Ve standardní knihovně v stdckdint.h je ckd_add
Ejhle, novinka :) Umí to někdo jiný než clang? V godboltu jsem to jinde nerozjel. To je i __builtin_add_overflow přenositelnější.
-
spravne (a bez specifikace bitove sirky) takto:
~0u >> 1
O tom, jestli je to správně by se dalo diskutovat dlouho. Hlavně proto, že nemáme zadání, co má ten program vypsat. Čěkal bych, že správné chování by se pokud možno nemělo lišit mezi platformama.
-
Ono je zakazano pouzivat knihovny?
Aritmetiku s velkymi cisly resi gmp knihovna. Proc znovuvynalezat kolo?
https://gmplib.org/ (https://gmplib.org/)
GMP není úplně vhodné řešení, když počítám s jednoregistrovými čísly a akorát potřebuju nějak pořešit přetečení.
-
C zachovava pri rotaci znamenko protoze rotace je provadena na znamenkovem typu, dela tedy tzv. aritmeticky bit shift.
Ani náhodou. Right shift na signed integeru je v C implementation defined. Překladač to může přeložit jako arimetický, nebo jako logický shift. Výsledek bude samozřejmě úplně jiný. Ke zbytku toho, co jsi napsal, se nebudu vyjadřovat, je to stejně nesprávné jako tvoje tvrzení, že je to aritmetický bit shift.
Nerekl jsem ze to neni implementation defined. Naopak to zakonite musi byt implementation defined, protoze reprezentace signed cisel neni nijak specifikovana a samotna je implementation defined. Muze byt pres dvojkovy doplnek (jak uz jsem psal v tom zbytku co jsi necetl) a muze byt i jinak.
Proto nema smysl vubec ani neco takoveho psat - rotaci na signed cisle. Ja jsem vzdy delal veskere bitove operace na unsigned typech a nikdy jsem s tim nemel problem. Kdo se chce strelit do nohy, toho C samozrejme necha, protoze neobsahuje zadne vozeni za rucicku. Z tohoto pohledu je tedy C tezky jazyk - clovek musi presne vedet jak to dole funguje.
Cely ten pripad s rotaci signed integeru mi prijde jako umele vykonstruovany priklad ktery jsem treba ja v praxi nikdy nemel, nevim jak ostatni... Ale dava to smysl proc to tak je a tezko si predstavit ze by to melo byt jinak.
-
Ve standardní knihovně v stdckdint.h je ckd_add
Ejhle, novinka :) Umí to někdo jiný než clang? V godboltu jsem to jinde nerozjel. To je i __builtin_add_overflow přenositelnější.
Ještě to umí GCC od 14.1, ale snad se přidají i další, když už je to ve standardu.
-
spravne (a bez specifikace bitove sirky) takto:
~0u >> 1
O tom, jestli je to správně by se dalo diskutovat dlouho. Hlavně proto, že nemáme zadání, co má ten program vypsat. Čěkal bych, že správné chování by se pokud možno nemělo lišit mezi platformama.
tak predpokladam ze zadani je udelat bitovy shift o jedno misto doprava... toto je prave stejne chovani napric vsema platformama protoze zaprve to pouziva ~0 misto -1 (ktere se muze lisit tam kde nejsou signed cisla implementovany dvojkovym doplnkem) a zadruhe je oprace provadena na unsigned integeru (0u), cili tam je jasne definovane jaky bude vysledek.
-
Cely ten pripad s rotaci signed integeru mi prijde jako umele vykonstruovany priklad ktery jsem treba ja v praxi nikdy nemel, nevim jak ostatni... Ale dava to smysl proc to tak je a tezko si predstavit ze by to melo byt jinak.
To je tvůj problém, že bitovým rotacím nerozumíš. Arithmetic right shift na signed čísle je jasně definovaný a je přesně dané, co to dělá a jsou na to CPU instrukce. Problém C je v tom, že přesně nedefinuje, co má rotace na signed čísle dělat. Standard dává možnost přeložit to jako aritmetický, nebo logický shift. Je to jeden z tisíce způsobů, jak se v C střelit do nohy, protože většína lidí předpokládá, že je to arimetický shift. Ale nemusí být, když to překladač C přeloží jako logický shift, je to taky podle C standardu v pořádku.
-
Ono je zakazano pouzivat knihovny?
Aritmetiku s velkymi cisly resi gmp knihovna. Proc znovuvynalezat kolo?
https://gmplib.org/ (https://gmplib.org/)
GMP není úplně vhodné řešení, když počítám s jednoregistrovými čísly a akorát potřebuju nějak pořešit přetečení.
Tak jasne no, C jako takove nerekne jestli operace pretece nebo ne - nema pristup k Carry flagu. Rekl bych ze to je opet proto ze ne vsechny architektury resi carry flag stejne. Mozna jsou i nejake ktere ho nemaji vubec. C musi byt prenositelne, to je jeho zakladni vlastnost.
Ale moznost jak detekovat preteceni v C existuje, clovek si muze lehce napsat funkci ktera mu rekne jestli vysledek scitani pretece nebo ne. Druha moznost je to implementovat pomoci assembleru a udrzovat header pro vsechny podporovane architektury - ta moznost tu je. Ale neni toto problem ve vsech jazycich? Je nejaky jazyk ktery nativne vyuzije carry flag? Nevolaji vsechny jen nejakou funkci ktera detekuje preteceni, podobne jako treba tato funkce v C?
bool will_addition_overflow(unsigned int a, unsigned int b)
{
return a > UINT_MAX - b;
}
-
Cely ten pripad s rotaci signed integeru mi prijde jako umele vykonstruovany priklad ktery jsem treba ja v praxi nikdy nemel, nevim jak ostatni... Ale dava to smysl proc to tak je a tezko si predstavit ze by to melo byt jinak.
To je tvůj problém, že bitovým rotacím nerozumíš. Arithmetic right shift na signed čísle je jasně definovaný a je přesně dané, co to dělá a jsou na to CPU instrukce. Problém C je v tom, že přesně nedefinuje, co má rotace na signed čísle dělat. Standard dává možnost přeložit to jako aritmetický, nebo logický shift. Je to jeden z tisíce způsobů, jak se v C střelit do nohy, protože většína lidí předpokládá, že je to arimetický shift. Ale nemusí být, když to překladač C přeloží jako logický shift, je to taky podle C standardu v pořádku.
pouze u signed cisel, u unsigned je operace definovana exaktne... mozna bys mohl mene urazet a vice chapat... neni to zadny problem, protoze provadet takove operace na signed cislech je implementation defined z podstaty, protoze samotna signed aritmetika je implementation defined
-
Cely ten pripad s rotaci signed integeru mi prijde jako umele vykonstruovany priklad ktery jsem treba ja v praxi nikdy nemel, nevim jak ostatni... Ale dava to smysl proc to tak je a tezko si predstavit ze by to melo byt jinak.
To je tvůj problém, že bitovým rotacím nerozumíš. Arithmetic right shift na signed čísle je jasně definovaný a je přesně dané, co to dělá a jsou na to CPU instrukce. Problém C je v tom, že přesně nedefinuje, co má rotace na signed čísle dělat. Standard dává možnost přeložit to jako aritmetický, nebo logický shift. Je to jeden z tisíce způsobů, jak se v C střelit do nohy, protože většína lidí předpokládá, že je to arimetický shift. Ale nemusí být, když to překladač C přeloží jako logický shift, je to taky podle C standardu v pořádku.
pouze u signed cisel, u unsigned je operace definovana exaktne... mozna bys mohl mene urazet a vice chapat... neni to zadny problem, protoze provadet takove operace na signed cislech je implementation defined z podstaty, protoze samotna signed aritmetika je implementation defined
ten kdo neco takoveho pouzije, musi mit duvod operovat se signed cisly a presne vedet co dela...
normalne clovek pouzije unsigned cisla a je vymalovano...
-
pouze u signed cisel, u unsigned je operace definovana exaktne... mozna bys mohl mene urazet a vice chapat... neni to zadny problem, protoze provadet takove operace na signed cislech je implementation defined z podstaty, protoze samotna signed aritmetika je implementation defined
Mohl bys prosím vysvětlit, co je neexaktního na signed arithmetic right shiftu? Je to naprosto běžná operace, která je definována exaktně. Říkat, že signed aritmetika je implementation defined, je opravdu hodně silné kafe. Není.
-
pouze u signed cisel, u unsigned je operace definovana exaktne... mozna bys mohl mene urazet a vice chapat... neni to zadny problem, protoze provadet takove operace na signed cislech je implementation defined z podstaty, protoze samotna signed aritmetika je implementation defined
Mohl bys prosím vysvětlit, co je neexaktního na signed arithmetic right shiftu? Je to naprosto běžná operace, která je definována exaktně. Říkat, že signed aritmetika je implementation defined, je opravdu hodně silné kafe. Není.
Nikde neni v C garantovano jak jsou implementovana signed cisla. Dvojkovy doplnek je jen pouze jedna z moznosti, ackoliv bezna praxe. Takze -1 = 0xffffffff, klidne muze existovat architektura kde treba signed bit je na nultem bitu a nebo nejaka uplne jina exoticka reprezentace, proste to neni dano. A to je presne duvod proc je implementation defined right shift operace na signed integeru.
> Mohl bys prosím vysvětlit, co je neexaktního na signed arithmetic right shiftu?
Tak sam jsi tvrdil ze je implementation defined, tak co na tom mam vysvetlovat? Na dane architekture nevis jaky bude vysledek, dokud nevyzkousis jaka je implementace...
-
Je a neni. Imho pokud vezmes jazyk jako takovy ( bez knihoven ) tak te mohou potrapit v podstate jenom ukazatele. Opet zalezi na tom jestli clovek neco nekdy delal napriklad v asembleru, protoze pak je to chapani veci mnohem jednodussi. To co cinni psani programu v C "slozitym" je "tvoje odpovednost za vse" typicky alokace/dealokace pameti, aligment atd. C je krasny a mocny jazyk, imho nepravem opomijeny v dnesni dobe.
-
Říkat, že signed aritmetika je implementation defined, je opravdu hodně silné kafe. Není.
Tak tady mas kavicku k obedu :D
UNIVAC 1100/2200 Series: These computers used one's complement arithmetic.
Similar to the VAX, they represented negative numbers by inverting all the bits of the
positive number.
CDC 6000 Series: The Control Data Corporation's CDC 6000 series, including models
like the CDC 6600, also used one's complement representation for signed integers.
IBM 7090/7094: These are examples of older IBM mainframes that employed
sign-magnitude representation for signed numbers, rather than one's or two's complement.
In the sign-magnitude system, the most significant bit is used as a sign bit, with the remaining
bits representing the magnitude of the number.
Takze pokud se bavine o bit shiftu unsigned integeru - tam je vse jasne, proste vezmes binarni reprezentaci a posunes vse o jeden bit doprava... Ale signed integer? Tady je kamen urazu. Protoze na kazde z techto architektur je stejny signed integer reprezentovan ruznym binarnim cislem, tak jaky ma byt presne vysledek bit shiftu?
Pokud stejny jako shift unsigned integeru, pak ztraci smysl arithmetic bit shift.
A pokud ruzny, tak timpadem musi byt implementation defined, protoze bud a nebo...
-
Ale moznost jak detekovat preteceni v C existuje, clovek si muze lehce napsat funkci ktera mu rekne jestli vysledek scitani pretece nebo ne.
Oki, ukážeš mi jak lehce napsat takovou funkci pro signed inty?
Ve většině jazyků je definované, co se při přetečení signed intu stane. Když vím, že mi to při přetečení wrapne, tak se taková funkce dá opravdu relativně lehce napsat.
-
Nikde neni v C garantovano jak jsou implementovana signed cisla.
V posledním C23 standardu už je to garantované, signed čísla musí být implementována jako druhý doplněk. Měli šanci opravit right shift na signed číslech, ale neudělali to, v C23 je pořád tohle:
The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1/2E2 . If E1 has a signed type and a negative value, the resulting value is implementation-defined
Na dane architekture nevis jaky bude vysledek, dokud nevyzkousis jaka je implementace...
Ale to je problém jen C! Ostatní jazyky přesně specifikují, co right shift na signed číslech dělá a není to implementation defined, např. v Rustu je to vždy arithmetic shift.
-
normalne clovek pouzije unsigned cisla a je vymalovano...
Pokud člověk zrovna nepíše nějaký mission critical kód, kde má unsigned čísla zakázaná. Ano, tohle se opravdu dělá, protože nenápadná automatická konverze z intu na uint je recept na katastrofu.
-
Ale moznost jak detekovat preteceni v C existuje, clovek si muze lehce napsat funkci ktera mu rekne jestli vysledek scitani pretece nebo ne.
Oki, ukážeš mi jak lehce napsat takovou funkci pro signed inty?
Ve většině jazyků je definované, co se při přetečení signed intu stane. Když vím, že mi to při přetečení wrapne, tak se taková funkce dá opravdu relativně lehce napsat.
bool add_will_overflow(int a, int b, int *result)
{
if (((b > 0) && (a > INT_MAX - b)) || // positive overflow
((b < 0) && (a < INT_MIN - b))) // negative overflow
return true;
*result = a + b;
return false; // no overflow
}
-
Nikde neni v C garantovano jak jsou implementovana signed cisla.
V posledním C23 standardu už je to garantované, signed čísla musí být implementována jako druhý doplněk. Měli šanci opravit right shift na signed číslech, ale neudělali to, v C23 je pořád tohle:
The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1/2E2 . If E1 has a signed type and a negative value, the resulting value is implementation-defined
Na dane architekture nevis jaky bude vysledek, dokud nevyzkousis jaka je implementace...
Ale to je problém jen C! Ostatní jazyky přesně specifikují, co right shift na signed číslech dělá a není to implementation defined, např. v Rustu je to vždy arithmetic shift.
Tak to ale prece neni zadny neprekonatelny problem... napises si na to inline funkci, ktera to dela tak jak chces (pomoci castu na unsigned integer), a tu vsude pouzivas a je to... proc to je implementation defined jsem tu vysvetloval a resitelne to taky je, takze nevidim zadny problem.
-
Ale moznost jak detekovat preteceni v C existuje, clovek si muze lehce napsat funkci ktera mu rekne jestli vysledek scitani pretece nebo ne.
Oki, ukážeš mi jak lehce napsat takovou funkci pro signed inty?
Ve většině jazyků je definované, co se při přetečení signed intu stane. Když vím, že mi to při přetečení wrapne, tak se taková funkce dá opravdu relativně lehce napsat.
bool add_will_overflow(int a, int b, int *result)
{
if (((b > 0) && (a > INT_MAX - b)) || // positive overflow
((b < 0) && (a < INT_MIN - b))) // negative overflow
return true;
*result = a + b;
return false; // no overflow
}
Supr, tohle vypadá že je i správně. :)
A co na to tazatel? Jak moc mu tohle přijde jednoduché? ;)
-
Ale moznost jak detekovat preteceni v C existuje, clovek si muze lehce napsat funkci ktera mu rekne jestli vysledek scitani pretece nebo ne.
Oki, ukážeš mi jak lehce napsat takovou funkci pro signed inty?
Ve většině jazyků je definované, co se při přetečení signed intu stane. Když vím, že mi to při přetečení wrapne, tak se taková funkce dá opravdu relativně lehce napsat.
bool add_will_overflow(int a, int b, int *result)
{
if (((b > 0) && (a > INT_MAX - b)) || // positive overflow
((b < 0) && (a < INT_MIN - b))) // negative overflow
return true;
*result = a + b;
return false; // no overflow
}
Není to undefined behavior, když b == INT_MIN? Přesněji není -b undefined?
-
Není to undefined behavior, když b == INT_MIN? Přesněji není -b undefined?
neni, proc by melo? :-) stejne jako 1-1 = 0 tak INT_MIN - INT_MIN = taky 0...
"to stejne" minus "to stejne" vzdycky bylo 0
-
A co na to tazatel? Jak moc mu tohle přijde jednoduché? ;)
Tak co je uvnitr je podle me fuk, pro tazatele dulezite, ze takto to muze zavolat:
int result;
if(add_will_overflow(0x90000000, 0x900000000, &result))
{
// handle overflow
}
-
Není to undefined behavior, když b == INT_MIN? Přesněji není -b undefined?
neni, proc by melo? :-) stejne jako 1-1 = 0 tak INT_MIN - INT_MIN = taky 0...
"to stejne" minus "to stejne" vzdycky bylo 0
Protože unární -INT_MIN je overflow. Nevím, jak binární. Ale třeba při použití zig cc (když Zigem kompilujeme C), tak takový program spadne na nedefinované chování i s binárním operátorem. Nebo, když to přeložím starým CompCertem, tak ten vypíše varování integer literal '2147483648' is too large to be represented in the enumeration integer type.
-
Protože unární -INT_MIN je overflow. Nevím, jak binární. Ale třeba při použití zig cc (když Zigem kompilujeme C), tak takový program spadne na nedefinované chování i s binárním operátorem. Nebo, když to přeložím starým CompCertem, tak ten vypíše varování integer literal '2147483648' is too large to be represented in the enumeration integer type.
Unarni minus, to neni nas pripad. Spadne kde a proc? Urcite na te instrukci pro odecet? Tak co ja vim co s tim zig cc udela... asi neni C compliant :D
Ohledne CompCertu me zrazi tato cast hlasky: to be represented in the enumeration integer type
Jaky enumeration integer type? Zadny enum nikde neni, tak nechapu proc by to neco takoveho melo psat...
Zadne upravy v kodu nejsou - kod je presne tak jak jsem poslal? A jak je to zavolano?
poprosil bych cely kod...
-
OT k těm bitovým a aritmetickým posunům. Ani jinde to není úplně růžové. Třeba Java je na jednu stranu mnohem specifičtější, ale posuny jsou omezeny na 31 resp. 32 bitů, což dokáže docela dobře zmást.
U céčka je situace jasná - musí běžet na čemkoli, od osmibitových MCU přes DSP až po 64bitové příšery. Takže spousta věcí je prostě závislá na architektuře.
-
Tak to ale prece neni zadny neprekonatelny problem... napises si na to inline funkci, ktera to dela tak jak chces (pomoci castu na unsigned integer)
Ale já nechci logical shift na unsigned integeru, chci arithmetic shift na signed integeru. Jak to udělám bezpečně v C? Neudělám, pokud si sám nenaimplementuji operaci arithmetic right shift. V ostatních jazycích to není problém, prostě použiju operátor >>, protože ve všech normálních jazycích je definované, že operátor >> dělá arithmetic right shift.
-
A co na to tazatel? Jak moc mu tohle přijde jednoduché? ;)
Tak co je uvnitr je podle me fuk, pro tazatele dulezite, ze takto to muze zavolat:
int result;
if(add_will_overflow(0x90000000, 0x900000000, &result))
{
// handle overflow
}
Jenže zavolat ji může jen když ji má. Jak nemá C23 tak tuhle funkci mu napíše jen C expert. A i to aby vůbec věděl, kdy tuhle funkci bude potřebovat, je zapotřebí nepříjemně mnoho znalostí.
Jde mi o to, že v C má i jednoduché počítání s intama spoustu překvapení. Není to jen tohle nedefinované chování. Jsou to i sequence pointy. To, že asociativita operátorů neříká nic o pořadí v jakém se vyhodnocuje složitější výraz. a můžu pokračovat. Jak v assembleru tak ve vysokoúrovňových jazycích jsou inty podstatně jednodušší než v C.
Základní věci jsou zbytečně (z dnešního pohledu) komplikované a složitější tam nejsou vůbec.
-
Ale já nechci logical shift na unsigned integeru, chci arithmetic shift na signed integeru. Jak to udělám bezpečně v C? Neudělám, pokud si sám nenaimplementuji operaci arithmetic right shift. V ostatních jazycích to není problém, prostě použiju operátor >>, protože ve všech normálních jazycích je definované, že operátor >> dělá arithmetic right shift.
A tvoje "normalni jazyky" umi bezet na procesorech ktere mohou mit tyto ruzne interpretace signed cisel?
Two's Complement
One's Complement
Sign and Magnitude
Offset Binary (Excess-K)
Binary Coded Decimal (BCD)
Base-2 Signed Digit (BSD)
Nebo treba na pocitaci s nebinarni logikou, jako sovetsky Setun?
Proste binarnich interpretaci signed cisel, a s tim souvisejici otazky jako "kde je znamenko" a "jestli tam vubec to znamenko je" neumoznuji udelat jednotny aritmeticky shift a proto je potreba si ho udelat rucne.
Podle me tvoje "normalni jazyky" toho jsou schopny jenom proto, ze se omezujou na architektury pouzivajici dvojkovy doplnek - cili nejsou prenositelne vsude. A nebo i v nich to musi byt implementation defined, akorat se o tom nemluvi...
Sice dvojkovy doplnek je univerzalni v tom smyslu ze kazdy procesor ktery umi unsigned aritmetiku umi i signed aritmetiku v dvojkovem doplnku - to je prave jeho vyhoda, ze pro procesor se nic nemeni oproti normalu. Ale je to SW reseni a nemusi byt treba tak optimalni jako HW reseni na dane platforme a proto C umoznuje oboji - umoznuje implementovat takovy, i onaky prekladac.
-
A tvoje "normalni jazyky" umi bezet na procesorech ktere mohou mit tyto ruzne interpretace signed cisel?
A potřebují to? Nebo jsou to jen zkamenělé pozůstatky muzejního hardwaru?
Vzhledem k tomu, že to nové normy vyrazily (v rámci možností zpětné kompatibility), je odpověď celkem jasná.
Všechno tohle už je jen technický dluh, který dělá Cčko složitějším.
-
Podle me tvoje "normalni jazyky" toho jsou schopny jenom proto, ze se omezujou na architektury pouzivajici dvojkovy doplnek - cili nejsou prenositelne vsude. A nebo i v nich to musi byt implementation defined, akorat se o tom nemluvi...
Sice dvojkovy doplnek je univerzalni v tom smyslu ze kazdy procesor ktery umi unsigned aritmetiku umi i signed aritmetiku v dvojkovem doplnku - to je prave jeho vyhoda, ze pro procesor se nic nemeni oproti normalu. Ale je to SW reseni a nemusi byt treba tak optimalni jako HW reseni na dane platforme a proto C umoznuje oboji - umoznuje implementovat takovy, i onaky prekladac.
Upozorňuju na to, že v "normálních" jazycích (pokud jimi myslíme Rust a spol.) existuje často nějaký způsob, jak tyto speciální věci udělat, byť to nemusí nutně přes operátor:
https://doc.rust-lang.org/stable/std/primitive.isize.html
-
A tvoje "normalni jazyky" umi bezet na procesorech ktere mohou mit tyto ruzne interpretace signed cisel?
A které dnes relevantní CPU používají pro reprezentaci signed čísel něco jiného než two's complement? Odpovím ti, žádné. Rezignovalo na ně dokonce i C a v nové verzi C23 standardu nic jiného než two's complement pro reprezentaci signed čísel nedovoluje.
Takže tady máme naprosto nerelevantní historické dědictví, které přidává další velmi elegantní způsob, jak se v C střelit do nohy použitím operátoru >> na signed integeru, protože v C (včetně C23) je to stále implementation defined. Člověk by čekal, že by bylo rozumné alespoň v C23 definovat, že je to arithmetic right shift, ale bohužel se to nestalo. Nevím, co standardizační komise C23 dělá, asi je běžní uživatelé C nezajímají. V C++ to už pochopili a od C++20 je operátor >> na signed integeru vždy arithmetic right shift.
Takový přístup chceš obhajovat, opravu ti přijde rozumné mít right shift implementation defined, když i C++20 to jasně definovalo jako arithmetic right shift?
-
Jenže zavolat ji může jen když ji má. Jak nemá C23 tak tuhle funkci mu napíše jen C expert. A i to aby vůbec věděl, kdy tuhle funkci bude potřebovat, je zapotřebí nepříjemně mnoho znalostí.
Jde mi o to, že v C má i jednoduché počítání s intama spoustu překvapení. Není to jen tohle nedefinované chování. Jsou to i sequence pointy. To, že asociativita operátorů neříká nic o pořadí v jakém se vyhodnocuje složitější výraz. a můžu pokračovat. Jak v assembleru tak ve vysokoúrovňových jazycích jsou inty podstatně jednodušší než v C.
Základní věci jsou zbytečně (z dnešního pohledu) komplikované a složitější tam nejsou vůbec.
Tak mit tu funkci, to je jen otazka toho mit danou knihovnu a nelisi se to nijak od napriklad pythoniho "import ..."
To uz je trochu polemika o tom co jeste ma a co uz nema byt soucasti standardu. Ale proc se omezovat na standard? Pokud je knihovna prenositelne napsana, tak muze byt pouzivana vsude a lze ji brat jako rozsireni standardu.
Knihovny by meli psat experti, novacek tam udela chyby, cili tady nevidim nic spatne - at pouzije knihovnu napsanou expertem, to je v poradku.
Kdy tuhle funkci bude potrebovat - tuto znalost musi mit ve vsech jazycich. Pokud se porad bavime o overflow jednointegerove promenne, tak ze je treba overflow ohandlovat - to je potreba vedet ve vsech jazycich. Kdyz to nebude vedet, neohandluje to ani v jinem jazyce.
Pokud chce clovek aby mu to vratilo inf pri overflow jako treba v Pythonu tak at si to naimplementuje - udela to jednou v zivote a pak uz to jen pouziva. Pokud prijme filozofii nekoho kdo uz toto vymyslel tak at pouziva jeho knihovnu. Nijak se to nelisi od prijeti filozofie jineho jazyka.
Pokud nechceme nikdy zadny overflow, tak na to uz jsem zminoval GMP knihovnu.
Neprijemne mnoho znalosti - proste kdo chce velkou kontrolu nad CPU, musi mit mnoho znalosti. Jestli je mu to neprijemne, tak to je jednoduche - at pouziva jiny jazyk :)
Respektuju nazor ale jsou to jenom subjektivni dojmy. Co je pro jednoho prekvapeni, muze byt pro jineho jasne logicky zaver.
Ja jsem se treba par let zpatky ucil C# a tak jak v C je mi vse jasne, tak C# mel pro me spoustu prekvapeni. Napriklad ze neco se predava hodnotou a neco referenci.
Proste C asi neni pro vsechny, ale to plati o vsem. Co je pro jednoho lehke, je pro jineho tezke.
Ale co bych doporucil tazatelovi je se ucit od nekoho kdo to zna - s nim konzultovat. Rict mu treba toto a toto mi prijde tezke, delam neco spatne, neda se to jinak? A zkuseny programator mu treba ukaze hned jak na to jednoduse.
-
A které dnes relevantní CPU používají pro reprezentaci signed čísel něco jiného než two's complement? Odpovím ti, žádné. Rezignovalo na ně dokonce i C a v nové verzi C23 standardu nic jiného než two's complement pro reprezentaci signed čísel nedovoluje.
Takže tady máme naprosto nerelevantní historické dědictví, které přidává další velmi elegantní způsob, jak se v C střelit do nohy použitím operátoru >> na signed integeru, protože v C (včetně C23) je to stále implementation defined. Člověk by čekal, že by bylo rozumné alespoň v C23 definovat, že je to arithmetic right shift, ale bohužel se to nestalo. Nevím, co standardizační komise C23 dělá, asi je běžní uživatelé C nezajímají. V C++ to už pochopili a od C++20 je operátor >> na signed integeru vždy arithmetic right shift.
Takový přístup chceš obhajovat, opravu ti přijde rozumné mít right shift implementation defined, když i C++20 to jasně definovalo jako arithmetic right shift?
Co to je "dnes relevantni"? Pro C je relevantni KAZDY procesor, odtud PRENOSITELNOST. To ze pro tebe neco neni relevantni nikoho nezajima, natoz pak tvurce standardu :D
Hehe prave naopak, C23 si PONECHAL moznost jine reprezentace signed cisel a dukazem je ze arithmetic right shift je stale implementation defined.
Proste prenositelnost a vykon jsou hlavni rysy jazyka C, takze umozuje i prekladac ktery pouziva jinou intepretaci zapornych cisel, pokud to HW podporuje.
Jestli to je okrajova zalezitost nebo ne, to C neresi. C umoznuje oboje.
To vubec neni o tom jestli mi neco prijde rozumne nebo jestli ja chci neco objajovat. Ja rikam proc to tam je, ze to ma svuj duvod a jak si to kdo vylozi je mi celkem fuk :D
Ze se nekdo streli do nohy, tak to je proste jen jeho neznalost, co na to rict. Snad aby si vybral nejakou bezpecnejsi zbran :-)
-
Ze se nekdo streli do nohy, tak to je proste jen jeho neznalost, co na to rict. Snad aby si vybral nejakou bezpecnejsi zbran :-)
Já bych tomu třeba říkal polovičatost.
-
Hehe prave naopak, C23 si PONECHAL moznost jine reprezentace signed cisel a dukazem je ze arithmetic right shift je stale implementation defined.
Přestaň už prosím psát nesmysly. Toto bylo akceptováno do C23 a NEDOVOLUJE to použít pro reprezentaci signed čísel nic jiného, než two's complement. Ke zbytku se nebudu vyjadřovat.
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2412.pdf
-
Já bych tomu třeba říkal polovičatost.
Zbrane nebo obsluhy? :)
-
Přestaň už prosím psát nesmysly. Toto bylo akceptováno do C23 a NEDOVOLUJE to použít pro reprezentaci signed čísel nic jiného, než two's complement. Ke zbytku se nebudu vyjadřovat.
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2412.pdf
Oukej, my bad. Toto jsem nevedel. V tomto ti davam za pravdu. Ale v principu to co jsem rekl plati, jen teda ne pro aritmetiku, ale pro bit shift jako takovy - tam proste rozdily na urovni HW mohou byt a prekladac muze cilit na optimalizovanou HW variantu.
-
Já bych tomu třeba říkal polovičatost.
Zbrane nebo obsluhy? :)
C začlo jako takový lepší Assembler. Takže je pochopitelné, že nebude aspirovat na nějaké úžasné záruky jako dnešní moderní sofistikované jazyky. To je cajk. Ale skutečnost, že nechává signed jako nedefinované mi přijde polovičaté. To máš stejné, jako kdyby byla nedefinovaná třeba konstrukce for jenom kůli tomu, že nějaký obskurní procesor nemá loopy. To není neznalost. To je jednoznačně chyba na straně C. Nemá to zkoušet vůbec rozlišovat signed a unsigned, a pak bych to bral. Takhle říká, že si to bere na starost, a přitom kecá.
-
Já bych tomu třeba říkal polovičatost.
Zbrane nebo obsluhy? :)
Ale skutečnost, že nechává signed jako nedefinované mi přijde polovičaté. To máš stejné, jako kdyby byla nedefinovaná třeba konstrukce for jenom kůli tomu, že nějaký obskurní procesor nemá loopy. To není neznalost. To je jednoznačně chyba na straně C. Nemá to zkoušet vůbec rozlišovat signed a unsigned, a pak bych to bral. Takhle říká, že si to bere na starost, a přitom kecá.
Jeden z důvodů, co jsem slyšel, je možnost více optimalizovat. Kompilátor může například předpokládat, že když k intu přičtete kladnou konstantu, tak výsledek bude vždy větší, než původní hodnota.
-
Ja sa z toho C asi snáď zbláznim :)
-
nedefinovaná třeba konstrukce for jenom kůli tomu, že nějaký obskurní procesor nemá loopy.
Tak zrovna porovnávat požadavek na konkrétní, byť naprosto majoritně nejrozšířenější, reprezentaci intů s požadavkem na Turing-kompletnost mi nepřijde jako moc vhodný argument.
Prostě to, že je chování nedefinované znamená, že se danou funkcni nemá používat, je to mrzuté (ale to jsem si už postěžoval na začátku) ale nic, co se nedá řešit knihovnou/makrem/...
Podstatné je, aby toho platform-závislého kódu nebyla většina, a tomu céčko IMHO vyhovuje (alespoň v rámci toho, co mám doma, i386, x86_64, arm, risc-v, atmel). A aby programátor nebyl čuně a neměl ty ifdefy rozházené po celém projektu, což ... radši budu potichu, i když většinou se to vyřeší během "ještě to dám na malinu" :D
-
Jenom takova technicka - k cemu je v C potreba operace posunu na signovane datove typy?
-
nedefinovaná třeba konstrukce for jenom kůli tomu, že nějaký obskurní procesor nemá loopy.
Tak zrovna porovnávat požadavek na konkrétní, byť naprosto majoritně nejrozšířenější, reprezentaci intů s požadavkem na Turing-kompletnost mi nepřijde jako moc vhodný argument.
Prostě to, že je chování nedefinované znamená, že se danou funkcni nemá používat, je to mrzuté (ale to jsem si už postěžoval na začátku) ale nic, co se nedá řešit knihovnou/makrem/...
V kontextu toho na co jsem reagoval, netvrdím tady, že je C k ničemu. Já tvrdím, že je to polovičaté, jako oponenturu tvrzení, že je to chyba programátora že si to má programátor nastudovat.
-
Já bych tomu třeba říkal polovičatost.
Zbrane nebo obsluhy? :)
Ale skutečnost, že nechává signed jako nedefinované mi přijde polovičaté. To máš stejné, jako kdyby byla nedefinovaná třeba konstrukce for jenom kůli tomu, že nějaký obskurní procesor nemá loopy. To není neznalost. To je jednoznačně chyba na straně C. Nemá to zkoušet vůbec rozlišovat signed a unsigned, a pak bych to bral. Takhle říká, že si to bere na starost, a přitom kecá.
Jeden z důvodů, co jsem slyšel, je možnost více optimalizovat. Kompilátor může například předpokládat, že když k intu přičtete kladnou konstantu, tak výsledek bude vždy větší, než původní hodnota.
Za cenu toho, že to občas bude počítat blbosti. Optimalizace jak stehno.
-
V kontextu toho na co jsem reagoval, netvrdím tady, že je C k ničemu. Já tvrdím, že je to polovičaté, jako oponenturu tvrzení, že je to chyba programátora že si to má programátor nastudovat.
Ale jó, takhle to beru.
Každá z těch našich lopat má nějaké vady na kráse. I když v případě Céčka a (když už jsme u těch rantů) než na tohle, tak bych spíš nadával na existenci strncpy() a (standardní) neexistenci strlcpy() https://man.freebsd.org/cgi/man.cgi?query=strlcpy&sektion=3 , což mě vadí přes víc než čtvrt století.
-
Já bych tomu třeba říkal polovičatost.
Zbrane nebo obsluhy? :)
Ale skutečnost, že nechává signed jako nedefinované mi přijde polovičaté. To máš stejné, jako kdyby byla nedefinovaná třeba konstrukce for jenom kůli tomu, že nějaký obskurní procesor nemá loopy. To není neznalost. To je jednoznačně chyba na straně C. Nemá to zkoušet vůbec rozlišovat signed a unsigned, a pak bych to bral. Takhle říká, že si to bere na starost, a přitom kecá.
Jeden z důvodů, co jsem slyšel, je možnost více optimalizovat. Kompilátor může například předpokládat, že když k intu přičtete kladnou konstantu, tak výsledek bude vždy větší, než původní hodnota.
Za cenu toho, že to občas bude počítat blbosti. Optimalizace jak stehno.
Já bych se toho samotného nedefinovaného chování zastal. Ono to umožňuje i řešit přetečení jako tvrdou chybu (-ftrapv). Přiznejme si, že při přetečení i v jiných jazycích obvykle nechceme wrap, ale je to něco co jsme neplánovali.
Ale tady zase nastupuje ta zmiňovaná polovičatost. Protože C nám nedává funkce pro ty výjimečné případy, kdy potřebujeme ten wrap.
-
Jenom takova technicka - k cemu je v C potreba operace posunu na signovane datove typy?
Taky jsem se chtěl zeptat. S tím se zase tak často člověk nesetká. S unsigned posuny ano, ale se signed?
-
Jenom takova technicka - k cemu je v C potreba operace posunu na signovane datove typy?
Taky jsem se chtěl zeptat. S tím se zase tak často člověk nesetká. S unsigned posuny ano, ale se signed?
Arithmetic right shift se dá použít na rychlé dělení konstantami 2, 4, 8, 16... signed čísel. I na současných CPU je integer dělení velmi drahá operace v porovnání s bit shiftem. Nelze přitom použít operátor dělení konstantou a doufat, že to překladač optimalizuje, protože integer dělení na signed integerech dává jiné výsledky než bitový posun. Výsledek operace (-1 / 2) je 0 a výsledek operace (-1 >> 1) je -1. Pokud se překladač rozhodne vygenerovat z operace dělení konstantou bitový posun, musí generovat extra kód, který tohle handluje.
Takže když chci extra rychlý kód pro dělení signed integerů konstantou a jsem smířen s tím, že to nedává identické výsledky jako operace dělení v C, je arithmetic right shift jediná varianta.
-
Jenom takova technicka - k cemu je v C potreba operace posunu na signovane datove typy?
Taky jsem se chtěl zeptat. S tím se zase tak často člověk nesetká. S unsigned posuny ano, ale se signed?
Arithmetic right shift se dá použít na rychlé dělení konstantami 2, 4, 8, 16... signed čísel. I na současných CPU je integer dělení velmi drahá operace v porovnání s bit shiftem. Nelze přitom použít operátor dělení konstantou a doufat, že to překladač optimalizuje, protože integer dělení na signed integerech dává jiné výsledky než bitový posun. Výsledek operace (-1 / 2) je 0 a výsledek operace (-1 >> 1) je -1. Pokud se překladač rozhodne vygenerovat z operace dělení konstantou bitový posun, musí generovat extra kód, který tohle handluje.
Takže když chci extra rychlý kód pro dělení signed integerů konstantou a jsem smířen s tím, že to nedává identické výsledky jako operace dělení v C, je arithmetic right shift jediná varianta.
jj já vím, co ta operace dělá. ale právě kvůli té problematické -1 (tedy samé jedničky v registru) to je IMHO vždycky způsob, jak se skutečně střelit do nohy. Vlastně právě i kvůli tomu, když píšu testy na kód se signed hodnotami, tak tam vždycky vrážím právě i -1, co to udělá.
docela bych řekl, že kdo toto potřebuje v praxi, tak prakticky vždycky by to měla být unsigned hodnota. Nějaký průchod polem dělením intervalu atd. - nezáporné indexy atd. Akorát unsigned je dlouhý slovo, tak jsme líní to psát...
-
jj já vím, co ta operace dělá. ale právě kvůli té problematické -1 (tedy samé jedničky v registru) to je IMHO vždycky způsob, jak se skutečně střelit do nohy. Vlastně právě i kvůli tomu, když píšu testy na kód se signed hodnotami, tak tam vždycky vrážím právě i -1, co to udělá.
docela bych řekl, že kdo toto potřebuje v praxi, tak prakticky vždycky by to měla být unsigned hodnota. Nějaký průchod polem dělením intervalu atd. - nezáporné indexy atd. Akorát unsigned je dlouhý slovo, tak jsme líní to psát...
Praktický případ - neuronka kvantizovaná do integer aritmetiky. Tam je fakt jedno, jestli je nějaká váha 0 nebo -1 v integeru, protože je to rozdí 0.0 vs něco jako -0.000001, což je na úrovni kvantizační chyby a nemá to vliv na výslednou accuracy.
-
Signed shift je užitečný, když potřebuju obecný sign-extend = rozkopírovat sign bit.
Třeba mám 48bit signed int a potřebuju z toho udělat 64: SHL16, SAR16 a je to.
-
jj já vím, co ta operace dělá. ale právě kvůli té problematické -1 (tedy samé jedničky v registru) to je IMHO vždycky způsob, jak se skutečně střelit do nohy. Vlastně právě i kvůli tomu, když píšu testy na kód se signed hodnotami, tak tam vždycky vrážím právě i -1, co to udělá.
docela bych řekl, že kdo toto potřebuje v praxi, tak prakticky vždycky by to měla být unsigned hodnota. Nějaký průchod polem dělením intervalu atd. - nezáporné indexy atd. Akorát unsigned je dlouhý slovo, tak jsme líní to psát...
Praktický případ - neuronka kvantizovaná do integer aritmetiky. Tam je fakt jedno, jestli je nějaká váha 0 nebo -1 v integeru, protože je to rozdí 0.0 vs něco jako -0.000001, což je na úrovni kvantizační chyby a nemá to vliv na výslednou accuracy.
Pravda, v tom případě by se to dalo použít. Takže v podstatě výpočet toho skalárního součinu s FX hodnotami na začátku každého neuronu a potom vydělení (posun) na správné místo řádové tečky. Dobře to je pěkný případ (i když popravdě se na to FX nehodí, možná ale není někdy zbytí, než mít jen integer aritmetiku).
-
Takže když chci extra rychlý kód pro dělení signed integerů konstantou a jsem smířen s tím, že to nedává identické výsledky jako operace dělení v C, je arithmetic right shift jediná varianta.
Není to jedinná varianta. Lze definovat makro a na platforme ktera nepodporuje signed aritmetic right shift se da jeho chovani simulovat.
#ifdef HAS_ARITHMETIC_SHIFT
// Check that option is set incorrectly
static_assert(-8 >> 1 == -4, "Signed arithmetic right shift not implemented.");
#define SARS(value, shift) ((value) >> (shift))
#else
BUD:
// pomoci marka - bez kontroly datovych typu
#define SARS(value, shift) (((value) < 0) ? ~(~(value) >> (shift)) : ((value) >> (shift)))
A NEBO:
// pomoci funkce - s kontrolou datovych typu
#define SARS(value, shift) arithmetic_right_shift((value), (shift))
int arithmetic_right_shift(int value, int shift);
#endif
Definice helper funkce arithmetic_right_shift():
int arithmetic_right_shift(int value, int shift)
{
return value < 0 ?
~(~value >> shift) :
(value >> shift);
}
Toto cele lze navic zakomponovat do build procesu a zautomatizovat:
// File: test_sars.c
#include <stdio.h>
int main()
{
int num = -8;
int shifted = num >> 1;
return (shifted == -4) ? 0 : 1;
}
Tento program build process spusti na zacatku a nastavi nekde option, neco jako:
if ./test_sars; then
echo "#define HAS_ARITHMETIC_SHIFT 1"
else
echo "#define HAS_ARITHMETIC_SHIFT 0"
fi
-
Ccko, narozdil od mnoha vsemoznych frikulinksych kravovin, po kterych za par let pes nestekne, dela presne to, co napises.
Jenže pokud nejsi velmi zkušený programátor, tak to znamená, že to velmi často dělá něco jiného, než chceš.
-
Ccko, narozdil od mnoha vsemoznych frikulinksych kravovin, po kterych za par let pes nestekne, dela presne to, co napises.
Jenže pokud nejsi velmi zkušený programátor, tak to znamená, že to velmi často dělá něco jiného, než chceš.
Nerozumim. Muzes dat nejaky priklad? Proc by mel delat _velmi casto_ neco jineho nez chci, kdyz ten jazyk je jednoduchy?
-
Ccko, narozdil od mnoha vsemoznych frikulinksych kravovin, po kterych za par let pes nestekne, dela presne to, co napises.
Jenže pokud nejsi velmi zkušený programátor, tak to znamená, že to velmi často dělá něco jiného, než chceš.
Nerozumim. Muzes dat nejaky priklad? Proc by mel delat _velmi casto_ neco jineho nez chci, kdyz ten jazyk je jednoduchy?
V CVE databázích je těch příkladů je spousta. (https://www.cve.org/CVERecord/SearchResults?query=memory+leak) Pokud to ani projekty jako Linux Kernel nebo GNU Coreutils nedokážou nedělat chyby při práci s pamětí, tak tím hůř se tomu ubrání někdo bez spousty praxe a citu.
-
Není to jedinná varianta. Lze definovat makro a na platforme ktera nepodporuje signed aritmetic right shift se da jeho chovani simulovat.
Tak to jste me s tim test_sars pekne nahlodal. Mam ve sve helper knihovne tuto variantu
int asr(int x, int n)
{
return (x >= 0) ? (x >> n) : ((x >> n) | (~((~0U) >> n)));
}
ktera je nezavisla na posuvu (arith/log), presne z toho duvodu, ze jsem nenasel elegantni zpusob, jak v compile time detekovat implementaci >>. Optimalizace v gcc i clang si s tim neporadi a konci to vzdy na sar + shr. Nastesti jsem to nikdy nepotreboval.
-
Já bych možná jen zmínil, že přestože jsem tu a tam historicky něco malého napsal, zhusta v céčku, tak shift na signed integer jsem co pamatuju nikdy nepotřeboval :-) takže ponaučení původnímu tazateli: jestli chcete tak se céčka bojte, ale ne kvůli shiftu se znaménkem :-)
-
V CVE databázích je těch příkladů je spousta. (https://www.cve.org/CVERecord/SearchResults?query=memory+leak) Pokud to ani projekty jako Linux Kernel nebo GNU Coreutils nedokážou nedělat chyby při práci s pamětí, tak tím hůř se tomu ubrání někdo bez spousty praxe a citu.
Mnohdy používají jen zbytečně komplikovaný přístup, kdy alokace spravují jednotlivě místo toho, aby to dělali hromadně.
Kdyby ten kód napsali jednodušeji, tak by měli méně problémů.
-
V CVE databázích je těch příkladů je spousta. (https://www.cve.org/CVERecord/SearchResults?query=memory+leak) Pokud to ani projekty jako Linux Kernel nebo GNU Coreutils nedokážou nedělat chyby při práci s pamětí, tak tím hůř se tomu ubrání někdo bez spousty praxe a citu.
Mnohdy používají jen zbytečně komplikovaný přístup, kdy alokace spravují jednotlivě místo toho, aby to dělali hromadně.
Kdyby ten kód napsali jednodušeji, tak by měli méně problémů.
A není to náhodou právě to, o co jde? Jazyk, který tě prostřílí jak řešeto jen proto, že nejsi dost dobrej...?
-
V CVE databázích je těch příkladů je spousta. (https://www.cve.org/CVERecord/SearchResults?query=memory+leak) Pokud to ani projekty jako Linux Kernel nebo GNU Coreutils nedokážou nedělat chyby při práci s pamětí, tak tím hůř se tomu ubrání někdo bez spousty praxe a citu.
Mnohdy používají jen zbytečně komplikovaný přístup, kdy alokace spravují jednotlivě místo toho, aby to dělali hromadně.
Kdyby ten kód napsali jednodušeji, tak by měli méně problémů.
Existuje na to nějaký dokument, který by popisoval "best practice" v takových případech? Ideálně s poukázáním na konkrétní historické incidenty?
-
V CVE databázích je těch příkladů je spousta. (https://www.cve.org/CVERecord/SearchResults?query=memory+leak) Pokud to ani projekty jako Linux Kernel nebo GNU Coreutils nedokážou nedělat chyby při práci s pamětí, tak tím hůř se tomu ubrání někdo bez spousty praxe a citu.
Mnohdy používají jen zbytečně komplikovaný přístup, kdy alokace spravují jednotlivě místo toho, aby to dělali hromadně.
Kdyby ten kód napsali jednodušeji, tak by měli méně problémů.
Existuje na to nějaký dokument, který by popisoval "best practice" v takových případech? Ideálně s poukázáním na konkrétní historické incidenty?
Nevím o tom.
Sám za sebe bych řekl: (1) preferovat statickou alokaci, (2) pokud nejde statická alokace preferovat arena alokátory nebo pooly, (3) pokud do nějaké struktury dávám ukazatel, tak ta struktura by neměla mít delší životnost než to, na co ukazuje ukazatel, (4) místo ukazatelů používat handly (z článku Handles are the better pointers (https://floooh.github.io/2018/06/17/handles-vs-pointers.html)), zejm. pokud si nejsem jistý životností objektů.
-
V CVE databázích je těch příkladů je spousta. (https://www.cve.org/CVERecord/SearchResults?query=memory+leak) Pokud to ani projekty jako Linux Kernel nebo GNU Coreutils nedokážou nedělat chyby při práci s pamětí, tak tím hůř se tomu ubrání někdo bez spousty praxe a citu.
Mnohdy používají jen zbytečně komplikovaný přístup, kdy alokace spravují jednotlivě místo toho, aby to dělali hromadně.
Kdyby ten kód napsali jednodušeji, tak by měli méně problémů.
Existuje na to nějaký dokument, který by popisoval "best practice" v takových případech? Ideálně s poukázáním na konkrétní historické incidenty?
Nevím o tom.
Sám za sebe bych řekl: (1) preferovat statickou alokaci, (2) pokud nejde statická alokace preferovat arena alokátory nebo pooly, (3) pokud do nějaké struktury dávám ukazatel, tak ta struktura by neměla mít delší životnost než to, na co ukazuje ukazatel, (4) místo ukazatelů používat handly (z článku Handles are the better pointers (https://floooh.github.io/2018/06/17/handles-vs-pointers.html)), zejm. pokud si nejsem jistý životností objektů.
Co mi to jen připomíná...
-
Ccko, narozdil od mnoha vsemoznych frikulinksych kravovin, po kterych za par let pes nestekne, dela presne to, co napises.
Jenže pokud nejsi velmi zkušený programátor, tak to znamená, že to velmi často dělá něco jiného, než chceš.
Nerozumim. Muzes dat nejaky priklad? Proc by mel delat _velmi casto_ neco jineho nez chci, kdyz ten jazyk je jednoduchy?
Tak schválně, co dělá tenhle nevinně vypadající kus kódu :
for (int i = 0; i < 4; ++i)
printf( "%d\n", i*1000000000 );
Odpověď zní že se může stát úplně cokoliv. :o V závislosti na platformě, překladači, náladě nosních démonů a fázi měsíce třeba :
- Vypíše to 4 čísla, které by člověk čekal. (obvykle debug, nebo staré překladače)
- Přeloží se to na nekonečnou smyčku. (novější překladače s optimalizacema)
- Vyoptimalizuje se to do pryč a vezme to ssebou půl programu. (pro tenhle konkrétní kód jsem to teda nepozoroval, ale je to legální a zažil jsem něco dost podobného)
Zkus tohle chování vysvětlit. Pokud možno _jednoduše_, když je C jednoduchý jazyk. 8)
-
Zkus tohle chování vysvětlit. Pokud možno _jednoduše_, když je C jednoduchý jazyk. 8)
Když to přeložím bez optimalizací - na gcc 15.1, vypíše to 4 čísla. Když to zkusím s optimalizací
dostanu zřetelný warning:
<code>
test.c: In function ‘main’:
test.c:7:9: warning: iteration 3 invokes undefined behavior [-Waggressive-loop-optimizations]
7 | printf( "%d\n", i*1000000000 );
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.c:6:20: note: within this loop
6 | for (int i = 0; i < 4; ++i)
| ~~^~~
</code>
-
Zkus tohle chování vysvětlit. Pokud možno _jednoduše_, když je C jednoduchý jazyk. 8)
Když to přeložím bez optimalizací - na gcc 15.1, vypíše to 4 čísla. Když to zkusím s optimalizací
dostanu zřetelný warning:
<code>
test.c: In function ‘main’:
test.c:7:9: warning: iteration 3 invokes undefined behavior [-Waggressive-loop-optimizations]
7 | printf( "%d\n", i*1000000000 );
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.c:6:20: note: within this loop
6 | for (int i = 0; i < 4; ++i)
| ~~^~~
</code>
Je to docela dobře vysvětlené https://stackoverflow.com/questions/24296571/why-does-this-loop-produce-warning-iteration-3u-invokes-undefined-behavior-an .
Holt optimalizace v C jsou opravdu agresivní - a opravdu se v Cčku nevyplatí ignorovat warningy
-
Je to docela dobře vysvětlené https://stackoverflow.com/questions/24296571/why-does-this-loop-produce-warning-iteration-3u-invokes-undefined-behavior-an .
Holt optimalizace v C jsou opravdu agresivní - a opravdu se v Cčku nevyplatí ignorovat warningy
Optimalizace založené na undefined behavior jsou, řekněmě, tak trochu za hranou. Formálně takovým optimalizacím nic nebrání (je to undefined, takže překladač může udělat cokoliv), nicméně obvykle nechceme, aby program dělal cokoliv. Nedávno vyšla docela hezká studie:
Exploiting Undefined Behavior in C/C++ Programs for Optimization: A Study on the Performance Impact (https://web.ist.utl.pt/nuno.lopes/pubs/ub-pldi25.pdf)
A její závěr je celkem jednoznačný: The results show that, in the cases we evaluated, the performance gains from exploiting UB are minimal. Furthermore, in the cases where performance regresses, it can often be recovered by either small to moderate changes to the compiler or by using link-time optimizations.
Konkrétně signed integer overflow zkoumají pod AO3 (vypnuli to pomocí -fwrapv) a rozdíly ve výkonu jsou zanedbatelné.
-
Konkrétně signed integer overflow zkoumají pod AO3 (vypnuli to pomocí -fwrapv) a rozdíly ve výkonu jsou zanedbatelné.
Ono je otázkou, co je dneska implementovatelné v překladači z pohledu zpětné kompatibility a očekávání uživatelů.
V každém případě moderní překladač důrazně upozorní, že je něco jinak, a je na vývojáři aby napsal kód bez warningu. V Postgresu kód s warningem nemá šanci se dostat do upstreamu, a co jsem se jako vývojář naučil, že warning v Cčku není něco, nad čím by se dalo mávnout rukou.
-
V každém případě moderní překladač důrazně upozorní, že je něco jinak, a je na vývojáři aby napsal kód bez warningu. V Postgresu kód s warningem nemá šanci se dostat do upstreamu, a co jsem se jako vývojář naučil, že warning v Cčku není něco, nad čím by se dalo mávnout rukou.
Warning řeší jen malou část poblému. Warning uvidíš jen v případě, kdy překladač může ověřit, že k signed overflow dojde, tozn. jedná se o konstanty známe v době překladu. V případě, kdy se sčítají dva integery a jejich hodnota není známa v době překladu, tak nikde žádný warning nebude, ale pořád je to undefined behavior a pořád se může stát v runtime cokoliv. C překladač má "volnost" vygenerovat kód, který bude řešit signed oveflow v runtime např. vynulováním proměnné, nebo to může být overflow s wrap, nebo taky může klidně zavolat exit... Všechno je legální.
-
V každém případě moderní překladač důrazně upozorní, že je něco jinak, a je na vývojáři aby napsal kód bez warningu. V Postgresu kód s warningem nemá šanci se dostat do upstreamu, a co jsem se jako vývojář naučil, že warning v Cčku není něco, nad čím by se dalo mávnout rukou.
Warning řeší jen malou část poblému. Warning uvidíš jen v případě, kdy překladač může ověřit, že k signed overflow dojde, tozn. jedná se o konstanty známe v době překladu. V případě, kdy se sčítají dva integery a jejich hodnota není známa v době překladu, tak nikde žádný warning nebude, ale pořád je to undefined behavior a pořád se může stát v runtime cokoliv. C překladač má "volnost" vygenerovat kód, který bude řešit signed oveflow v runtime např. vynulováním proměnné, nebo to může být overflow s wrap, nebo taky může klidně zavolat exit... Všechno je legální.
Ano, např :
bool test(int i)
{
return i+1 > i;
}
se zoptimalizuje na return true a to bez jakéhokoliv warningu.
-
Ccko, narozdil od mnoha vsemoznych frikulinksych kravovin, po kterych za par let pes nestekne, dela presne to, co napises.
Jenže pokud nejsi velmi zkušený programátor, tak to znamená, že to velmi často dělá něco jiného, než chceš.
Nerozumim. Muzes dat nejaky priklad? Proc by mel delat _velmi casto_ neco jineho nez chci, kdyz ten jazyk je jednoduchy?
Tak schválně, co dělá tenhle nevinně vypadající kus kódu :
for (int i = 0; i < 4; ++i)
printf( "%d\n", i*1000000000 );
Odpověď zní že se může stát úplně cokoliv. :o V závislosti na platformě, překladači, náladě nosních démonů a fázi měsíce třeba :
- Vypíše to 4 čísla, které by člověk čekal. (obvykle debug, nebo staré překladače)
- Přeloží se to na nekonečnou smyčku. (novější překladače s optimalizacema)
- Vyoptimalizuje se to do pryč a vezme to ssebou půl programu. (pro tenhle konkrétní kód jsem to teda nepozoroval, ale je to legální a zažil jsem něco dost podobného)
Zkus tohle chování vysvětlit. Pokud možno _jednoduše_, když je C jednoduchý jazyk. 8)
V C jsem naposled programoval na VS, ale jestli tohle kompilátor přeloží na nekonečnou smyčku i s warningem, tak to jsou cunata.
A ten side effect je popsány ve specifikaci?
-
Konkrétně signed integer overflow zkoumají pod AO3 (vypnuli to pomocí -fwrapv) a rozdíly ve výkonu jsou zanedbatelné.
Ono je otázkou, co je dneska implementovatelné v překladači z pohledu zpětné kompatibility a očekávání uživatelů.
V každém případě moderní překladač důrazně upozorní, že je něco jinak, a je na vývojáři aby napsal kód bez warningu. V Postgresu kód s warningem nemá šanci se dostat do upstreamu, a co jsem se jako vývojář naučil, že warning v Cčku není něco, nad čím by se dalo mávnout rukou.
Od toho tu jsou switche jako třeba -std=xxx. Nečekám zcela plnou zpětnou kompatibilitu, pokud chci použít nové vlastnosti, právě proto, že nové vlastnosti odstraňují (měly by) spoustu starých nepěkností.
-
A ten side effect je popsány ve specifikaci?
Je to podle specifikace - kompilátor si může dělat, co chce, v případě přetečení signed intu. Nicméně některé kompilátory dovolují zvolit chování, co se má stát v případě přetečení (ale to už specifikace C nepožaduje, to je navíc, nepovinná vlastnost).
-
Jestli tady ještě OP čte, tak za mě pro učení C je ideální Arduino. Člověka potěší i ta rozblikaná LEDka. Také je to low level, a vyšší jazyky se tam nevejdou, takže C je rozumná volba. Rozblikaný kurzor v terminálu nemá takové kouzlo.
Poslat něco po sériové lince, přerušení, atd.
Pak se dá třeba na ESP napsat hloupý HTTP server.
-
V C jsem naposled programoval na VS, ale jestli tohle kompilátor přeloží na nekonečnou smyčku i s warningem, tak to jsou cunata.
A ten side effect je popsány ve specifikaci?
Tam je problém, že ten kus kódu nemá žádné správné chování.
Norma C říká, že když jako programátor dopustíš, aby ti kdekoliv v kódu přetekl int, tak dává od celého tvého programu ruce pryč.
Překladače se snaží obvykle udělat něco příčetného, ale ne vždycky se to dá.
-
Jestli tady ještě OP čte, tak za mě pro učení C je ideální Arduino. Člověka potěší i ta rozblikaná LEDka. Také je to low level, a vyšší jazyky se tam nevejdou, takže C je rozumná volba. Rozblikaný kurzor v terminálu nemá takové kouzlo.
Poslat něco po sériové lince, přerušení, atd.
Pak se dá třeba na ESP napsat hloupý HTTP server.
Nepíše sa pre Arduino v C++ náhodou?
-
Jestli tady ještě OP čte, tak za mě pro učení C je ideální Arduino. Člověka potěší i ta rozblikaná LEDka. Také je to low level, a vyšší jazyky se tam nevejdou, takže C je rozumná volba. Rozblikaný kurzor v terminálu nemá takové kouzlo.
Poslat něco po sériové lince, přerušení, atd.
Pak se dá třeba na ESP napsat hloupý HTTP server.
Nepíše sa pre Arduino v C++ náhodou?
hodinky, holinky... typickej arduinista
-
Jestli tady ještě OP čte, tak za mě pro učení C je ideální Arduino. Člověka potěší i ta rozblikaná LEDka. Také je to low level, a vyšší jazyky se tam nevejdou, takže C je rozumná volba. Rozblikaný kurzor v terminálu nemá takové kouzlo.
Poslat něco po sériové lince, přerušení, atd.
Pak se dá třeba na ESP napsat hloupý HTTP server.
Nepíše sa pre Arduino v C++ náhodou?
hodinky, holinky... typickej arduinista
Tak ono C++ podědilo většinu chuťovek z C. Některé se podařilo trochu učesat a jiným zase C++ umožnilo rozvinout svůj destruktivní potenciál. :)
-
Není to jedinná varianta. Lze definovat makro a na platforme ktera nepodporuje signed aritmetic right shift se da jeho chovani simulovat.
Tak to jste me s tim test_sars pekne nahlodal. Mam ve sve helper knihovne tuto variantu
int asr(int x, int n)
{
return (x >= 0) ? (x >> n) : ((x >> n) | (~((~0U) >> n)));
}
ktera je nezavisla na posuvu (arith/log), presne z toho duvodu, ze jsem nenasel elegantni zpusob, jak v compile time detekovat implementaci >>. Optimalizace v gcc i clang si s tim neporadi a konci to vzdy na sar + shr. Nastesti jsem to nikdy nepotreboval.
Ano, při kompile-timu to opravdu detekovat nelze. Proto jsem přidal extra krok do buildovacího procesu - nejdříve se spustí testovací program, ten zjístí jak to daný překladač na oné architektuře má, a následně se na základě výsledku spustí buď optimalizovaný, nebo konzervativní překlad. Je to krok navíc v buildovacím procesu, ale umožňuje přeložit optimalizovaný kód - to jenom že Linuxák říkal, že když chci optimalizovaný kód, tak jiná cesta než spolehnout se na undefined behaviour není. Toto řešení má obojí. Kde to jde, tam je optimalizované, a kde to nejde, tak používá vlastní defined-behaviour implementaci.
-
Ano, při kompile-timu to opravdu detekovat nelze. Proto jsem přidal extra krok do buildovacího procesu - nejdříve se spustí testovací program, ten zjístí jak to daný překladač na oné architektuře má, a následně se na základě výsledku spustí buď optimalizovaný, nebo konzervativní překlad. Je to krok navíc v buildovacím procesu, ale umožňuje přeložit optimalizovaný kód - to jenom že Linuxák říkal, že když chci optimalizovaný kód, tak jiná cesta než spolehnout se na undefined behaviour není. Toto řešení má obojí. Kde to jde, tam je optimalizované, a kde to nejde, tak používá vlastní defined-behaviour implementaci.
Děkuji, to je hezká ukázka, jak je C prakticky velmi špatně použitelné. Abych dokázal použít operátor >> v C správně, tak musím:
- Vědět, že je operátor >> implementation defined (většina lidí to neví a předpokládá, že se jedná o aritmetický shift).
- Implementovat testovací program, který mi na dané platformě zjistí, jak se věci mají.
- Implementovat vlastní operátor aritmetic right shift.
- Při buidu spustit testovací program.
- Podle výsledku testovacího programu udělat conditional compile.
- Měl bych mít testy na obě varianty.
A to vše jen proto, že si C táhne v dnešní době naprosto nerelevantní dědictví, kdy nechce definovat operátor >> na signed integeru jako aritmetický shift. Nemá to žádný praktický smysl, protože aktuálně neexistuje relevantní CPU, které by nebyla instrukce pro aritmetický shift.
-
Vědět, že je operátor >> implementation defined (většina lidí to neví a předpokládá, že se jedná o aritmetický shift).
Pak by si asi měli přečíst dokumentaci. Stejně tak by někdo mohl říct, že v Rustu většina lidí neví, že chování + závisí na tom, jestli kompilujete Debug nebo Release. Nebo v C#, že se chování ToUpper a ToLower řídí aktuálním locale a někdy můžete dostat fakt nečekané výsledky. Nebo v Gleamu, dělení 0 vrací 0, není to chyba. A takových situací jsou hromady ať už jste v Rustu, C, C++, Go nebo F#. Je lepší nepředpokládat a přečíst si dokumentaci.
-
A to vše jen proto, že si C táhne v dnešní době naprosto nerelevantní dědictví, kdy nechce definovat operátor >> na signed integeru jako aritmetický shift. Nemá to žádný praktický smysl, protože aktuálně neexistuje relevantní CPU, které by nebyla instrukce pro aritmetický shift.
Tak kdyby C neměl jiné výhody, tak budiž. On je ale má a i přes toto všechno má cenu ho zvolit. Mimo jiné jakmile toto jednou vyřeším, tak mám vyřešeno jednou pro vždy, takže mě to už při dalším vývoji neobtěžuje.
Navíc se to dá řešit i jinak, ale už by to nebyl optimalizovaný kód. Byl by tam nějaky kompromis pohodlí vývojáře vs. výkoon (jak to koneckonců ostatní jazyky automaticky mají)
Navíc z mé praxe - nikdy jsem na to nenarazil, je to vyumělkovaný příklad. A hodnotit celé C jen na základě tady této jedné pro tebe neštastné věci je prostě biased.
-
Vědět, že je operátor >> implementation defined (většina lidí to neví a předpokládá, že se jedná o aritmetický shift).
Pak by si asi měli přečíst dokumentaci. Stejně tak by někdo mohl říct, že v Rustu většina lidí neví, že chování + závisí na tom, jestli kompilujete Debug nebo Release. Nebo v C#, že se chování ToUpper a ToLower řídí aktuálním locale a někdy můžete dostat fakt nečekané výsledky. Nebo v Gleamu, dělení 0 vrací 0, není to chyba. A takových situací jsou hromady ať už jste v Rustu, C, C++, Go nebo F#. Je lepší nepředpokládat a přečíst si dokumentaci.
Přesně tak. Znalost jazyka je nutná pro každý jazyk. A řeči typu "jazyk neděla co chci" = jen lenost si ho nastudovat. Každý jazyk se musí nastudovat, i ty moderní. Jestli to je někomu bližší, a snažší uchopit, tak budiž, ale ve výsledku je potřeba to nastudovat a tomu se člověk nevyhne ani v tom Rustu, ani C#. Každý jazyk má svá specifika a toto jsou dobré příklady že i v jiných jazycích se lze lehce střelit do nohy.
-
Vědět, že je operátor >> implementation defined (většina lidí to neví a předpokládá, že se jedná o aritmetický shift).
Já jsem převážně používal bitové posuny na různé maskování a bitové operace a tam jsem logicky volil unsigned formát protože proč bych to měl mít signed když nepotřebuji záporná čísla.
Dále jsem používal pro rychlé násobení a dělení mocninama dvou a tam jsem vždy taky operoval raději na unsigned číslech, protože vím že záporná čísla mají nejvyšší bit vždy nahozen a tak logicky přichází otázka: co když zrotuju doleva a nahodí se nejvyšší bit - číslo se stane záporné - to bych nechtěl. Nebo co když zrotuju doprava a číslo ztratí svoje znaménko. Prostě bitové operace jsou low level operace a timpádem člověk musí vědět jak to dole skutečně vypadá.
Pokud si někdo klade stejné otázky, tak se taky automaticky raději vyhne jakýmkoliv bitovým operacím na signed číslech. A pokud ne, tak to je buď naivka a nebo expert který přesně ví co dělá. Jak říkal tady kolega - člověk by neměl předpokládat nic, měl by vše alespoň jednou v životě přečíst v dokumentaci. Programovat na základě předpokladů je dobrý hazard a doufám že to tak dělají jenom vyjímky a není to nový standard.
-
Navíc z mé praxe - nikdy jsem na to nenarazil, je to vyumělkovaný příklad.
To je argument jak noha. Nikdy jsem na to nenarazil, takže je to vyumělkovaný příklad... Dá se to napsat i jinak, nikdy jsem na to nenarazil, protože mám málo zkušeností, nebo jsem to prostě ve své praxi nikdy nepotřeboval. Extrapolovat, že je to vyumělkovaný příklad, je tak trochu nezkušenost.
Abych tě trochu uvedl do praxe, aritmetický posun vpravo se hodí k rychlému dělení signed integerů. Instrukci integer dělení nechceš, je na všech současných CPU podstatně dražší. Operátor dělení v C taky nechceš, protože dává jiné výsledky než aritmetický shift a překladač to musí handlovat, i když dělení optimalizuje na aritmetický shift.
Pokud mám náhodou implementaci C, která místo aritmetického shiftu dělá logický (což norma povoluje), musím do assembleru, protože C implementace aritmetického posunu bude pomalejší než instrukce CPU (pokud překladač nepozná, že dělám aritmetický posun a nezoptimalizuje to, což ale není zaručeno).
-
Děkuji, to je hezká ukázka, jak je C prakticky velmi špatně použitelné. Abych dokázal použít operátor >> v C správně, tak musím:
- Vědět, že je operátor >> implementation defined (většina lidí to neví a předpokládá, že se jedná o aritmetický shift).
...
A to vše jen proto, že si C táhne v dnešní době naprosto nerelevantní dědictví, kdy nechce definovat operátor >> na signed integeru jako aritmetický shift. Nemá to žádný praktický smysl, protože aktuálně neexistuje relevantní CPU, které by nebyla instrukce pro aritmetický shift.
Jediny bod 1 je relevantni, je treba vedet, jak je neco definovano. To se obavam, ze je podminkou nutnou kdekoli, a pro kazdy jazyk existuji stranky a videa na tema vsech moznych a nemoznych chytaku.
Zbyle body se tykaji toho, ze norma rika "nelizej v zime zabradli", tak jak to udelat, aby presto clovek to zabradli oliznul a nedopadlo to spatne.
Navic se tam resi i optimalizace, coz uz jsme na uplne novem levelu proti "nevi, jak ten operator funguje".[/list]
-
Pak by si asi měli přečíst dokumentaci.
To je validní argument, je to v dokumentaci. Nicméně čtení dokumentace C (a její aplikace v praxi) připomíná procházku minovým polem. Je tam opravdu hodně undefined a implementation defined behavior. To je ten problém, na který se snažím poukázat. A možnosti zlepšení jsou, ale jaksi se neuplatňují. Např. u operátoru >> stačí zadefinovat, že je to aritmetický shift na signed integeru a všichni by měli život jednodušší. C++20 už to udělalo, C23 ne.
-
Navíc z mé praxe - nikdy jsem na to nenarazil, je to vyumělkovaný příklad.
To je argument jak noha. Nikdy jsem na to nenarazil, takže je to vyumělkovaný příklad... Dá se to napsat i jinak, nikdy jsem na to nenarazil, protože mám málo zkušeností, nebo jsem to prostě ve své praxi nikdy nepotřeboval. Extrapolovat, že je to vyumělkovaný příklad, je tak trochu nezkušenost.
Abych tě trochu uvedl do praxe, aritmetický posun vpravo se hodí k rychlému dělení signed integerů. Instrukci integer dělení nechceš, je na všech současných CPU podstatně dražší. Operátor dělení v C taky nechceš, protože dává jiné výsledky než aritmetický shift a překladač to musí handlovat, i když dělení optimalizuje na aritmetický shift.
Pokud mám náhodou implementaci C, která místo aritmetického shiftu dělá logický (což norma povoluje), musím do assembleru, protože C implementace aritmetického posunu bude pomalejší než instrukce CPU (pokud překladač nepozná, že dělám aritmetický posun a nezoptimalizuje to, což ale není zaručeno).
Heh naopak jsem mel dost zkusenosti na to, ze jsem si uvedomil ze to je implementation defined, protoze reprezentace signed cisel v C vzdy byla (az po C23) implementation defined a radeji tedy operoval na unsigned variante (jak jsem psal).
Lol - uvedl me do praxe :D Ja mam v C embedded programovani praxi 17 let.
Vyumelkovany proto, ze se cela diskuze toci jenom kolem jednoho undefined signed arithmetic shiftu. O nejakem podelanem shiftu ten jazyk vubec neni.
-
Vědět, že je operátor >> implementation defined (většina lidí to neví a předpokládá, že se jedná o aritmetický shift).
Pak by si asi měli přečíst dokumentaci. Stejně tak by někdo mohl říct, že v Rustu většina lidí neví, že chování + závisí na tom, jestli kompilujete Debug nebo Release. Nebo v C#, že se chování ToUpper a ToLower řídí aktuálním locale a někdy můžete dostat fakt nečekané výsledky. Nebo v Gleamu, dělení 0 vrací 0, není to chyba. A takových situací jsou hromady ať už jste v Rustu, C, C++, Go nebo F#. Je lepší nepředpokládat a přečíst si dokumentaci.
Přesně tak. Znalost jazyka je nutná pro každý jazyk. A řeči typu "jazyk neděla co chci" = jen lenost si ho nastudovat. Každý jazyk se musí nastudovat, i ty moderní. Jestli to je někomu bližší, a snažší uchopit, tak budiž, ale ve výsledku je potřeba to nastudovat a tomu se člověk nevyhne ani v tom Rustu, ani C#. Každý jazyk má svá specifika a toto jsou dobré příklady že i v jiných jazycích se lze lehce střelit do nohy.
Pamatuju si, že velice podobnou argumentaci jsem četl i v diskuzi pod jedním legendárním Javascriptovým WTF. Tam se potkaly cca 3 perfektně zdokumentované konverze a výsledkem byl totálně nepochopitelný mindfuck. 8)
A proto máme i věci jako "princip minimálního překvapení", protože každá drobnost kterou neodhadneme a musíme ji nastudovat z dokumentace nám bere čas a mentální kapacitu.
-
Pokud mám náhodou implementaci C, která místo aritmetického shiftu dělá logický (což norma povoluje), musím do assembleru, protože C implementace aritmetického posunu bude pomalejší než instrukce CPU (pokud překladač nepozná, že dělám aritmetický posun a nezoptimalizuje to, což ale není zaručeno).
Pokud překladač něco nedělá, a mermomocí to tam chceš, tak vždy musíš do assembleru, čímž ale ztrácíš portabilitu žejo. To se netýka jenom arithmetic shiftu, ale čehokoliv. V dokumentaci je, že je to implementation specific, takže když někdo očekává že to tam bude, tak je to naivka. Tohle mi příjde argument jak noha...
-
Vědět, že je operátor >> implementation defined (většina lidí to neví a předpokládá, že se jedná o aritmetický shift).
Pak by si asi měli přečíst dokumentaci. Stejně tak by někdo mohl říct, že v Rustu většina lidí neví, že chování + závisí na tom, jestli kompilujete Debug nebo Release. Nebo v C#, že se chování ToUpper a ToLower řídí aktuálním locale a někdy můžete dostat fakt nečekané výsledky. Nebo v Gleamu, dělení 0 vrací 0, není to chyba. A takových situací jsou hromady ať už jste v Rustu, C, C++, Go nebo F#. Je lepší nepředpokládat a přečíst si dokumentaci.
Přesně tak. Znalost jazyka je nutná pro každý jazyk. A řeči typu "jazyk neděla co chci" = jen lenost si ho nastudovat. Každý jazyk se musí nastudovat, i ty moderní. Jestli to je někomu bližší, a snažší uchopit, tak budiž, ale ve výsledku je potřeba to nastudovat a tomu se člověk nevyhne ani v tom Rustu, ani C#. Každý jazyk má svá specifika a toto jsou dobré příklady že i v jiných jazycích se lze lehce střelit do nohy.
Pamatuju si, že velice podobnou argumentaci jsem četl i v diskuzi pod jedním legendárním Javascriptovým WTF. Tam se potkaly cca 3 perfektně zdokumentované konverze a výsledkem byl totálně nepochopitelný mindfuck. 8)
A proto máme i věci jako "princip minimálního překvapení", protože každá drobnost kterou neodhadneme a musíme ji nastudovat z dokumentace nám bere čas a mentální kapacitu.
princip minimálního překvapení je v C plně implementován a jmenuje se unsigned int - ten jsem ve své praxi vždy použil a nikdy jsem překvapen nebyl
-
Vyumelkovany proto, ze se cela diskuze toci jenom kolem jednoho undefined signed arithmetic shiftu. O nejakem podelanem shiftu ten jazyk vubec neni.
Jop, C není o jednom podělaném shiftu.
Ale tahle diskuze je o tom jestli je C těžké. A to, že tu můžeme takhle složitě rozebírat chování jednoho podělaného shiftu, tu obtížnost ilustruje naprosto dokonale.
-
Heh naopak jsem mel dost zkusenosti na to, ze jsem si uvedomil ze to je implementation defined
Dovol, abych ti připomenul tohle:
C zachovava pri rotaci znamenko protoze rotace je provadena na znamenkovem typu, dela tedy tzv. aritmeticky bit shift.
Lol - uvedl me do praxe :D Ja mam v C embedded programovani praxi 17 let.
Tak je 17 let asi málo, ještě minulý týden jsi žil v domění, že je to arimetický shift a ne implementation defined. Jsem rád, že jsem ti pomohl rozšířit znalosti C. Není zač.
-
Heh naopak jsem mel dost zkusenosti na to, ze jsem si uvedomil ze to je implementation defined
Dovol, abych ti připomenul tohle:
C zachovava pri rotaci znamenko protoze rotace je provadena na znamenkovem typu, dela tedy tzv. aritmeticky bit shift.
Jedine v cem js mi rozsiril znalosti je ze Cěš enforcuje dvojkovy doplnek, zato diky. Zbytek jsi mne spise nepochopil. Jinak o tve neustale utoky nestojim tak si je muzes smerovat jinam, diky :-) Arrogant prick...
Lol - uvedl me do praxe :D Ja mam v C embedded programovani praxi 17 let.
Tak je 17 let asi málo, ještě minulý týden jsi žil v domění, že je to arimetický shift a ne implementation defined. Jsem rád, že jsem ti pomohl rozšířit znalosti C. Není zač.
Jedine o cos mi rozsiril znalosti je ze C23 enforcuje dvojkovy doplnek. Za to diky. Zbytek bych spise rekl ze jsi mne nepochopil, nebo je taky mozne ze jsem se ve spechu nekde spatne vyjadril. Ale jinak o tve neustale utoky v diskuzi nestojim a muzes si je smerovat jinam, diky :-) Arrogant prick...
-
Jedine o cos mi rozsiril znalosti je ze C23 enforcuje dvojkovy doplnek. Za to diky. Zbytek bych spise rekl ze jsi mne nepochopil, nebo je taky mozne ze jsem se ve spechu nekde spatne vyjadril. Ale jinak o tve neustale utoky v diskuzi nestojim a muzes si je smerovat jinam, diky :-) Arrogant prick...
Hele, já na tebe neútočím. Měli jsme nejasnosti ohledně chování operátoru >> v C a vysvětlili jsme si, že je to implementation defined. Měli jsme nejasnosti ohledně reprezentace signed čísel v C23 a vysvětlili jsme si, že to musí být druhý doplněk. Což já považuji za skvělou příležitost redefinovat chování operátoru >> jako arithmetic shift, přesně jak se stalo v C++20. Standardizační komise C23 a ty tento názor bohužel nesdílí, nedá si nic dělat, budeme muset dál žít s implementation defined.
-
princip minimálního překvapení je v C plně implementován a jmenuje se unsigned int - ten jsem ve své praxi vždy použil a nikdy jsem překvapen nebyl
unsigned x = 1;
if ( x < -1 )
printf( "Co se sakra děje?" );
Unsigned v C(++) má vestavěné tak zákeřné překvápko, že je v některých mission critical systémech kompletně zakázaný. :)
-
princip minimálního překvapení je v C plně implementován a jmenuje se unsigned int - ten jsem ve své praxi vždy použil a nikdy jsem překvapen nebyl
unsigned x = 1;
if ( x < -1 )
printf( "Co se sakra děje?" );
Unsigned v C(++) má vestavěné tak zákeřné překvápko, že je v některých mission critical systémech kompletně zakázaný. :)
A kde je jaké překvápko? Překvapen může být akorát zase jenom ten kdo něco předpokládá. Ten kdo zná pořadí operátorů a implicitní konverze datových typů tak v tom má jasno.
Jinak tady není důvod aby to překladač nepodchytil:
547818386/source.c:6:12: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
if ( x < -1 )
^
-
princip minimálního překvapení je v C plně implementován a jmenuje se unsigned int - ten jsem ve své praxi vždy použil a nikdy jsem překvapen nebyl
unsigned x = 1;
if ( x < -1 )
printf( "Co se sakra děje?" );
Unsigned v C(++) má vestavěné tak zákeřné překvápko, že je v některých mission critical systémech kompletně zakázaný. :)
A kde je jaké překvápko? Překvapen může být akorát zase jenom ten kdo něco předpokládá. Ten kdo zná pořadí operátorů a implicitní konverze datových typů tak v tom má jasno.
A ještě si musí všimnout, že se mu signed a unsigned potkaly. Stačí aby to x nebylo deklarované hned vedle toho ifu a je to.
Jinak tady není důvod aby to překladač nepodchytil:
547818386/source.c:6:12: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
if ( x < -1 )
^
Nějaký důvod tam bude, když MSVC i clang mlčí jak partizáni. A i gcc potřebuje -Wall, jinak drží hubu.
U jiných věcí mi překladače nadávají daleko ochotněji. Tenhle warning z nich člověk musí spíš páčit.
-
A ještě si musí všimnout, že se mu signed a unsigned potkaly. Stačí aby to x nebylo deklarované hned vedle toho ifu a je to.
Ne, on by si neměl všimnout. On jde něco porovnávát, tak musí vědět co jde porovnávat. Nemůžu přece něco porovnávat pokud nevím jakého to je datového typu a tedy s čím to můžu (kompatibilní datové typy) porovnat. Jak můžu bez těchto informací vůbec jít něco porovnávat?
Nějaký důvod tam bude, když MSVC i clang mlčí jak partizáni. A i gcc potřebuje -Wall, jinak drží hubu.
U jiných věcí mi překladače nadávají daleko ochotněji. Tenhle warning z nich člověk musí spíš páčit.
Warning na to ale je - na všechny porovnání signed a unsigned proměnných/konstant dostaneš warning. Critical mission politika v tomto ohledu je zastaralá, asi vychází z dob kdy kompilátory toto hlásit neuměly, nebo nevím proč raději nepoužít warningy a místo toho všude rvát inty, kde zase mohou vznikat jiné chyby které by s unsigned nebyly.
-
Je sranda když si někdo stežuje na "undefined behaviour", ale přitom píše kód stylem "wishful programming", kdy něco očekává - že jazyk by se měl chovat tak nebo onak a je to chyba jazyka když tomu tak není.
Tak na jednu stranu je špatně undefined behaviour a na druhou stranu defined behaviour, kde -1 se převede na unsigned a pak porovná, je taky špatně. Tak už si vyberte :)
-
A ještě si musí všimnout, že se mu signed a unsigned potkaly. Stačí aby to x nebylo deklarované hned vedle toho ifu a je to.
Ne, on by si neměl všimnout. On jde něco porovnávát, tak musí vědět co jde porovnávat. Nemůžu přece něco porovnávat pokud nevím jakého to je datového typu a tedy s čím to můžu (kompatibilní datové typy) porovnat. Jak můžu bez těchto informací vůbec jít něco porovnávat?
Víte co mě zaráží? Že si nějaký vývojář ani po 17 letech neuvědomuje, jak jednoduché je udělat chybu. Že my lidi udržíme v hlavě najednou jen cca 10 věcí. A každá věc, co za nás nehlídá překladač a musíme ji hlídat my nám z té konečné mentální kapacity užírá.
Nějaký důvod tam bude, když MSVC i clang mlčí jak partizáni. A i gcc potřebuje -Wall, jinak drží hubu.
U jiných věcí mi překladače nadávají daleko ochotněji. Tenhle warning z nich člověk musí spíš páčit.
Warning na to ale je - na všechny porovnání signed a unsigned proměnných/konstant dostaneš warning. Critical mission politika v tomto ohledu je zastaralá, asi vychází z dob kdy kompilátory toto hlásit neuměly, nebo nevím proč raději nepoužít warningy a místo toho všude rvát inty, kde zase mohou vznikat jiné chyby které by s unsigned nebyly.
Je v jen v jednom ze tří nejpoužívanějších překladačů. A ani v něm není zapnutý by default. Takový warning spíš není než je.
-
Je sranda když si někdo stežuje na "undefined behaviour", ale přitom píše kód stylem "wishful programming", kdy něco očekává - že jazyk by se měl chovat tak nebo onak a je to chyba jazyka když tomu tak není.
Tak na jednu stranu je špatně undefined behaviour a na druhou stranu defined behaviour, kde -1 se převede na unsigned a pak porovná, je taky špatně. Tak už si vyberte :)
Ano špatně je nedefinované i blbě definované chování. Zkuste chvíli psát v třeba assembleru. Nepotkal jsem žádný jazyk, který by blbé inty dokázal zkomplikovat tak, jako to udělalo C.
-
Tady tuto věc ale překladač hlídá, sic ne by default.
Tak ono to je asi prostě o tom jak kdo přemýšlí. Nedávno jsem četl jak jeden britský turista mluvil s jedním indánem, bavili se o lovu a ten brit řekl jednoduše že vystřelil. A to tomu indánovi přišlo strašně podivné. Protože to je pro ně uplně neexistující pojem. Oni tak nepřemýšlí. Oni když řeknou že vystřelili, tak u toho vždycky řeknou na co mířili a co tou střelou chtěli dokázat - jaký měla účel. A tady vidím podobnost. Prostě já když jdu něco porovnávat, tak vím co jdu porovnávat a s čím a bez toho to nemůžu ani udělat. Nemůžu "jen tak porovnat". Porovnávám vždy něco s něčím.
No a pokud je něčí záměr úplný a jen se třeba přeťukl ve znaménku nebo operátoru, tak oukej, ale ať to nenazývá že tam je nějaké překvápko. Překvápko = neznalost... A to je úplně něco jiného než že někdo udělá překlep nebo jinou chybu.
Dále nechápu to s tím assemblerem. C přesně odpovídá tomu jak jsou věci v assembleru. Je to de-facto portabilní assembler. A právě ten kdo prozkoumá věci v C až na úroveň asembleru, tak ten má pak v těch věcech jasno a žádná mentální zátěž to pro něj už není. Mentální zátěž je, když v tom jasno právě nemá a musí to pořád vyhodnocovat.
-
A ještě si musí všimnout, že se mu signed a unsigned potkaly. Stačí aby to x nebylo deklarované hned vedle toho ifu a je to.
Ne, on by si neměl všimnout. On jde něco porovnávát, tak musí vědět co jde porovnávat. Nemůžu přece něco porovnávat pokud nevím jakého to je datového typu a tedy s čím to můžu (kompatibilní datové typy) porovnat. Jak můžu bez těchto informací vůbec jít něco porovnávat?
Což v reálném světě funguje asi tak do situace, kdy se to porovnání napsalo před rokem, porovnává se nějaký atribut ve struktuře nebo jiném netriviálním datovém typu, a někdo jiný o rok později v té struktuře změní ten int na unsigned int. A aby to chytlo -Wall, to by na tom projektu muselo pracovat tak pět lidí pod tvrdým dohledem, kdy jakýkoliv warning je špatně, a ne pět set, s přicmrdáváním někde z Indie, kdy ten jeden důležitý warning se utopí v hromadě jiných. A jasně, podobné věci se dějí bez ohledu na jazyk. Akorát jak tahle diskuze ukazuje v tom C je prostě o dost víc možností, jak si ustřelit vlastní nohu a při tom udělat bezpečnostní zranitelnost.
-
Večerní C kvíz. Proč se tento program chová tak divně a proč -Wall nic neřekne? Kdo to vyřeší, dostane odkaz do gcc bugzilly ;)
#include <stdio.h>
int main()
{
static int array[2] = {0, 1};
int *a = &array[1];
unsigned i = 0;
a += i - 1;
printf("%d\n", *a);
return 0;
}
Nejprve to přeložme a spusťme bez optimalizací:
gcc -Wall main.c -o test
./test
Neoprávněný přístup do paměti (SIGSEGV)
A teď s optimalizacemi:
gcc -O2 -Wall main.c -o test
./test
0
-
A ještě si musí všimnout, že se mu signed a unsigned potkaly. Stačí aby to x nebylo deklarované hned vedle toho ifu a je to.
Ne, on by si neměl všimnout. On jde něco porovnávát, tak musí vědět co jde porovnávat. Nemůžu přece něco porovnávat pokud nevím jakého to je datového typu a tedy s čím to můžu (kompatibilní datové typy) porovnat. Jak můžu bez těchto informací vůbec jít něco porovnávat?
Což v reálném světě funguje asi tak do situace, kdy se to porovnání napsalo před rokem, porovnává se nějaký atribut ve struktuře nebo jiném netriviálním datovém typu, a někdo jiný o rok později v té struktuře změní ten int na unsigned int. A aby to chytlo -Wall, to by na tom projektu muselo pracovat tak pět lidí pod tvrdým dohledem, kdy jakýkoliv warning je špatně, a ne pět set, s přicmrdáváním někde z Indie, kdy ten jeden důležitý warning se utopí v hromadě jiných. A jasně, podobné věci se dějí bez ohledu na jazyk. Akorát jak tahle diskuze ukazuje v tom C je prostě o dost víc možností, jak si ustřelit vlastní nohu a při tom udělat bezpečnostní zranitelnost.
Tak základ je mít warningy zapnuté a mít projekt ve stavu že nedává žádný warning. To už je na projekt manažerovi aby to podchytil, kde mít warningy a jaké.
A jak by to teda mělo být správně? Warning nestačí? To se má celé C změnit protože se lidi strílejí do nohy? A co performance a všechno s tím spojené... zahodit?
-
Večerní C kvíz. Proč se tento program chová tak divně a proč -Wall nic neřekne? Kdo to vyřeší, dostane odkaz do gcc bugzilly ;)
#include <stdio.h>
int main()
{
static int array[2] = {0, 1};
int *a = &array[1];
unsigned i = 0;
a += i - 1;
printf("%d\n", *a);
return 0;
}
Nejprve to přeložme a spusťme bez optimalizací:
gcc -Wall main.c -o test
./test
Neoprávněný přístup do paměti (SIGSEGV)
A teď s optimalizacemi:
gcc -O2 -Wall main.c -o test
./test
0
Další příklad "wishful programmingu". Proč máš i unsigned když chceš dělat signed aritmetiku a mít výsledek -1?
a += 0u - 1
což je to samé jako
a += 0xffffffff
Vysvětli proč jsi použil unsigned
-
Další příklad "wishful programmingu". Proč máš i unsigned když chceš dělat signed aritmetiku a mít výsledek -1?
Ano, všichni víme, že ty bys žádnou podobnout chybu nikdy neudělal, ale kde je ten slibovaný warning, který měl být s -Wall vidět?
-
Další příklad "wishful programmingu". Proč máš i unsigned když chceš dělat signed aritmetiku a mít výsledek -1?
Ano, všichni víme, že ty bys žádnou podobnout chybu nikdy neudělal, ale kde je ten slibovaný warning, který měl být s -Wall vidět?
Toto je normální runtime podtečení, proč by to mělo dávat warning?
-
Toto je normální runtime podtečení, proč by to mělo dávat warning?
Protože to má dát tento warning, ale z nějakého důvodu nedá. Pokračuj v řešení kvízu, jsi na dobré cestě ;)
main.c: In function ‘main’:
main.c:10:5: warning: array subscript 4294967296 is outside array bounds of ‘int[2]’ [-Warray-bounds]
-
Toto je normální runtime podtečení, proč by to mělo dávat warning?
Protože to má dát tento warning, ale z nějakého důvodu nedá. Pokračuj v řešení kvízu, jsi na dobré cestě ;)
main.c: In function ‘main’:
main.c:10:5: warning: array subscript 4294967296 is outside array bounds of ‘int[2]’ [-Warray-bounds]
Ne, k datům se přistupuje přes pointer, tak proč by to mělo psát že array out of bounds?
-
Ne, k datům se přistupuje přes pointer, tak proč by to mělo psát že array out of bounds?
gcc ví, že pointer je alias na to původní pole. V tomto kódu warning dostaneš:
#include <stdio.h>
int main()
{
int array[2] = {0, 1};
int *a = &array[1];
a += 1;
printf("%d\n", *a);
return 0;
}
gcc -Wall -O2 main.c -o test
main.c: In function ‘main’:
main.c:8:5: warning: array subscript 2 is outside array bounds of ‘int[2]’ [-Warray-bounds]
8 | printf("%d\n", *a);
| ^~~~~~~~~~~~~~~~~~
main.c:5:9: note: at offset 8 into object ‘array’ of size 8
5 | int array[2] = {0, 1};
| ^~~~~
No ale v tom původním přikladu žádný warning není a navíc to bez optimalizací spadne, ale s optimalizacemi dává "správný" výsledek. Záhada, že? ;)
-
Tak základ je mít warningy zapnuté a mít projekt ve stavu že nedává žádný warning. To už je na projekt manažerovi aby to podchytil, kde mít warningy a jaké.
A jak by to teda mělo být správně? Warning nestačí? To se má celé C změnit protože se lidi strílejí do nohy? A co performance a všechno s tím spojené... zahodit?
To, co tu tvrdíme už nějakou dobu. Nechat C jen tam, kde je to nezbytně nutné. A tam, kde chceš performance, ale není nutně důvod použít C, použít třeba Rust. Který dá +- podobný výkon, ale z principu vyloučí velmi časté chyby z C.
Proč lidi tolik trvají na tom, že i šroub musí zatlouct kladivem, jen proto, že je to o něco rychlejší, než najít ten šroubovák? :D
-
Vyumelkovany proto, ze se cela diskuze toci jenom kolem jednoho undefined signed arithmetic shiftu. O nejakem podelanem shiftu ten jazyk vubec neni.
Jop, C není o jednom podělaném shiftu.
Ale tahle diskuze je o tom jestli je C těžké. A to, že tu můžeme takhle složitě rozebírat chování jednoho podělaného shiftu, tu obtížnost ilustruje naprosto dokonale.
OT: ciste na zaklade tohoto operatoru jde naprosto to stejny rict o Jave, kde zrovna shifty taky pekne ohnuli, takze je to plny prekvapeni.
-
OT: ciste na zaklade tohoto operatoru...
Nejde iba o jeden operátor. Hlavný problém je to, že ľudia nechápu, čo je to nedefinované správanie, prečo existuje, čo spôsobuje a kam až siahajú jeho účinky.
A z toho potom niekedy vznikajú situácie, keď je program z dôvodu nedefinovaného správania neplatný, ale zdá sa, že funguje.
-
Nejde iba o jeden operátor. Hlavný problém je to, že ľudia nechápu, čo je to nedefinované správanie, prečo existuje, čo spôsobuje a kam až siahajú jeho účinky.
Ono existuje? UB se přece nikdy neděje. Ty jako programátor ses o to přece postaral a překladač ti v tom bezmezně věří. 8)
Občas mám pocit, že samotná slova jako "chování", "způsobuje" a podobné zastírají podstatu UB a proč je to občas takový mindfuck. Ta představa, že to UB něco dělá, je svým způsobem strašně špatně.
-
To, co tu tvrdíme už nějakou dobu. Nechat C jen tam, kde je to nezbytně nutné. A tam, kde chceš performance, ale není nutně důvod použít C, použít třeba Rust. Který dá +- podobný výkon, ale z principu vyloučí velmi časté chyby z C.
Proč lidi tolik trvají na tom, že i šroub musí zatlouct kladivem, jen proto, že je to o něco rychlejší, než najít ten šroubovák? :D
Ono psát nějaké datové struktury v Rustu je celkem chuťovka. Ve standardní knihovně se k tomu používá unsafe Rust a opět vám hrozí nedefinované chování kvůli aliasingu. Navíc řešíte řešíte problémy, které v C nejsou, jako například, jak vhodně použít PhantomData.
-
To, co tu tvrdíme už nějakou dobu. Nechat C jen tam, kde je to nezbytně nutné. A tam, kde chceš performance, ale není nutně důvod použít C, použít třeba Rust. Který dá +- podobný výkon, ale z principu vyloučí velmi časté chyby z C.
Proč lidi tolik trvají na tom, že i šroub musí zatlouct kladivem, jen proto, že je to o něco rychlejší, než najít ten šroubovák? :D
Ono psát nějaké datové struktury v Rustu je celkem chuťovka. Ve standardní knihovně se k tomu používá unsafe Rust a opět vám hrozí nedefinované chování kvůli aliasingu. Navíc řešíte řešíte problémy, které v C nejsou, jako například, jak vhodně použít PhantomData.
Protože ty problémy v C jsou. Schované. Rust Vás jen donutí je explicitně pojmenovat a vyřešit.
Tady prezentovaná zásadní výhoda C - výkon a přímé napojení na hw - je i jeho zásadní nevýhodou. Překladač nemá dost informací o vysokoúrovňovém záměru a nemůže tudíž aplikovat všechny optimalizace a kontroly.
-
Ono existuje? UB se přece nikdy neděje. Ty jako programátor ses o to přece postaral a překladač ti v tom bezmezně věří. 8)
Občas mám pocit, že samotná slova jako "chování", "způsobuje" a podobné zastírají podstatu UB a proč je to občas takový mindfuck. Ta představa, že to UB něco dělá, je svým způsobem strašně špatně.
To už celkom preháňate, nemyslíte? Alebo je to tak, že by ste si to mali do študovať?
-
...Rust Vás jen donutí je explicitně pojmenovat a vyřešit.
Jo tak proto to pri kompilaci sebe sama hlasi kazdej jeden radek kodu ze je vadnej ... teda nez ta kompilace zbuchne, protoze to samosebe zkopilovat neumi.
Zjevne kvalitka ....
-
Tady prezentovaná zásadní výhoda C - výkon a přímé napojení na hw - je i jeho zásadní nevýhodou. Překladač nemá dost informací o vysokoúrovňovém záměru a nemůže tudíž aplikovat všechny optimalizace a kontroly.
S tím přímým napojením na hw je to taky zajímavé. Ono je to spíš nadstandardní rozšíření různých dialektů, než něco ze standardního C.
Standardní C vám dlouho nedalo ani add with carry.
A díky strict aliasingu je v něm prakticky neimplementovatelné třeba rychlé memcpy.
Ono existuje? UB se přece nikdy neděje. Ty jako programátor ses o to přece postaral a překladač ti v tom bezmezně věří. 8)
Občas mám pocit, že samotná slova jako "chování", "způsobuje" a podobné zastírají podstatu UB a proč je to občas takový mindfuck. Ta představa, že to UB něco dělá, je svým způsobem strašně špatně.
To už celkom preháňate, nemyslíte? Alebo je to tak, že by ste si to mali do študovať?
Proč přeháním? UB stojí na tom, že nesmí nastat. Validní C programy neobsahují UB. Programátor má za úkol zajistit aby k němu nemohlo dojít.
Dereference NULL je UB -> když dereferencuju pointer, tak optimalizátor ví, že nikdy nemůže být NULL a může tuhle informaci využít jak uzná za vhodné.
Nebo třeba funkce
bool test(int x)
{
return x+1 > x
}
říká dvě věci :
1) + nikdy nepřeteče, takže test vždycky vrátí true. Takže z toho může optimalizátor udělat return true.
2) x nikdy nebude INT_MAX (protože by + přeteklo). Takže optimalizátor může tuhle informaci propagovat výš. Občas to může dotéct až překvapivě daleko.
Jen jsem se snažil trošku nezvyklým způsobem vystihnout tu neintuitivnost celého UB.
-
A hádáte se zbytečně. C totiž je těžké. Nejde jen o různé pasti s undefined behavior. Popravdě jste tu zatím všechny ukázky založili na jediné oblasti, čísla se znaménkem.
Hlavní důvod proč je C těžké, je neexistence knihovny standardních datových struktur a masivní použití (často netypovaných void) pointerů.
- C nemá strukturu pro asociativní pole (ať už hash nebo strom)
- C neumí pohlídat přetečení bufferu
- C neumí pohlídat use-after-free
Takže aby programátor neudělal logickou chybu, musí držet v hlavě celý datový model aplikace a vědět, komu patří který pointer, kdo je zodpovědný za uvolnění atd.
Proto vznikají články jako už zmíněný https://floooh.github.io/2018/06/17/handles-vs-pointers.html , proto máme valgrind (a taky glib nebo Qt/QML pro C++).
Ano, možnost toto všechno řídit ručně umožní vyždímat i poslední kousek výkonu. Tedy možná, protože dnešní procesory jsou tak složité, že optimalizace programátorem naopak stav často zhorší.
Naprostá většina aplikací toto nepotřebuje. Naprostá většina aplikací potřebuje spolehlivou logiku a datový model víc, než optimalizaci na HPC nebo absolutně nejnižší možnou latenci.
C je jen o malý schůdek nad assemblerem. A kolik kódu jste za poslední dekádu napsali v ASM? Obojí se hodí, až když uděláte profiling a zjistíte, že Vás opravdu omezuje funkce na násobení matice nebo něco podobného (https://www.laws-of-software.com/laws/knuth/ ).
Ale na zbytek aplikace je mnohem vhodnější použít něco, kde se můžete soustředit na to CO to má dělat místo na to, JAK to je implementované na nízké úrovni.
C++ má spoustu podobných pastí, ale alespoň má STL, takže máte smart pointery a nemusíte si psát věci jako Vector nebo iterátory pořád dokola. Rust zase vyžaduje pochopení lifetimes pro jakoukoliv složitější strukturu a je přísný při překladu.
-
...Rust Vás jen donutí je explicitně pojmenovat a vyřešit.
Jo tak proto to pri kompilaci sebe sama hlasi kazdej jeden radek kodu ze je vadnej ... teda nez ta kompilace zbuchne, protoze to samosebe zkopilovat neumi.
Zjevne kvalitka ....
Protože bootstraping překladače je něco, co děláme všichni každý den. Bootstraping gcc jsem dělal přesně dvakrát v životě. A taky to vyžadovalo binárky, pak statický build a pak teprve finální build s optimalizacemi a knihovnami.
A Rust navíc má zdokumentované proč to tak je. Kompilujete překladač s kódem, který teprve bude stabilní (v té nové verzi). Jelikož to Rust hlídá, tak to za normálních okolností nedovolí. Proto je tam pro bootstrap speciální proměnná, která ty nightly-only kontroly vypne.
https://rustc-dev-guide.rust-lang.org/building/bootstrapping/what-bootstrapping-does.html#complications-of-bootstrapping
-
...Rust Vás jen donutí je explicitně pojmenovat a vyřešit.
Jo tak proto to pri kompilaci sebe sama hlasi kazdej jeden radek kodu ze je vadnej ... teda nez ta kompilace zbuchne, protoze to samosebe zkopilovat neumi.
Zjevne kvalitka ....
Kam chceš tímto příspěvkem posunout debatu?
-
To, co tu tvrdíme už nějakou dobu. Nechat C jen tam, kde je to nezbytně nutné. A tam, kde chceš performance, ale není nutně důvod použít C, použít třeba Rust. Který dá +- podobný výkon, ale z principu vyloučí velmi časté chyby z C.
Proč lidi tolik trvají na tom, že i šroub musí zatlouct kladivem, jen proto, že je to o něco rychlejší, než najít ten šroubovák? :D
Ono psát nějaké datové struktury v Rustu je celkem chuťovka. Ve standardní knihovně se k tomu používá unsafe Rust a opět vám hrozí nedefinované chování kvůli aliasingu. Navíc řešíte řešíte problémy, které v C nejsou, jako například, jak vhodně použít PhantomData.
Protože ty problémy v C jsou. Schované. Rust Vás jen donutí je explicitně pojmenovat a vyřešit.
Tady prezentovaná zásadní výhoda C - výkon a přímé napojení na hw - je i jeho zásadní nevýhodou. Překladač nemá dost informací o vysokoúrovňovém záměru a nemůže tudíž aplikovat všechny optimalizace a kontroly.
Praveze C umoznuje psat tak ci onak. Je to o tom jak si programator projekt zorganizuje.
-
Praveze C umoznuje psat tak ci onak. Je to o tom jak si programator projekt zorganizuje.
Programátor si může přidat komentáře a rozdělit soubory. Jenže překladač z toho tu sémantiku nepozná. Neví jaký je záměr. V Rustu můžeme (no spíš musíme) překladači říct, jak dlouho daný pointer žije. V C++ existuje alespoň unique_ptr / shared_ptr. V C to u pointeru není jak vyjádřit.
Což omezuje možnosti automatické analýzy a optimalizace. Takže to musí hlídat programátor a tím dochází k chybám, protože lidi dělají chyby.
-
Proč přeháním? UB stojí na tom, že nesmí nastat. Validní C programy neobsahují UB. Programátor má za úkol zajistit aby k němu nemohlo dojít.
Dereference NULL je UB -> když dereferencuju pointer, tak optimalizátor ví, že nikdy nemůže být NULL a může tuhle informaci využít jak uzná za vhodné.
Nebo třeba funkce
bool test(int x)
{
return x+1 > x
}
říká dvě věci :
1) + nikdy nepřeteče, takže test vždycky vrátí true. Takže z toho může optimalizátor udělat return true.
2) x nikdy nebude INT_MAX (protože by + přeteklo). Takže optimalizátor může tuhle informaci propagovat výš. Občas to může dotéct až překvapivě daleko.
Jen jsem se snažil trošku nezvyklým způsobem vystihnout tu neintuitivnost celého UB.
A jak zapíšu do registru který je na adrese 0x00000000, nebo jak si přečtu kam ukazuje reset vektor, kdyby mi to kompilátor nedovolil? Proč by mi to neměl dovolit?
bool test(int x)
{
return x+1 > x;
}
Při x == INT_MAX výsledek přeteče a je to UB.
Správný postup je nedovolit přetečení a ohandlovat ho:
bool test(int x)
{
return x < INT_MAX ? x + 1 > x : false; // nebo true? Co ja vim jak tento dalsi vyumelkovany nesmysl ma vypadat...
}
-
A jak zapíšu do registru který je na adrese 0x00000000, nebo jak si přečtu kam ukazuje reset vektor, kdyby mi to kompilátor nedovolil? Proč by mi to neměl dovolit?
No blbě. Pokud to vaše platforma potřebuje, tak vám musí dát nějaký nestandardní způsob jak to udělat. Standardní C to neumí (pokud teda NULL odpovídá adrese 0, to taky nemusí platit).
Správný postup je nedovolit přetečení a ohandlovat ho:
Správný postup je napsat všechno bez chyb. Jenže my lidi to jaksi neumíme. A proto tu řešíme co mi jazyk udělá, když se seknu a něco neohandlím.
-
A jak zapíšu do registru který je na adrese 0x00000000
Nezapíšeš. Protože gcc ti na tohle, v závislosti na optimalizacích, verzi překladače a fázi měsíce vygeneruje ud2 isntrukci. A proč? Protože může, je to undefined behavior, norma takové chování povoluje.
https://godbolt.org/z/bGqsEqsYc
-
A jak zapíšu do registru který je na adrese 0x00000000
Nezapíšeš. Protože gcc ti na tohle, v závislosti na optimalizacích, verzi překladače a fázi měsíce vygeneruje ud2 isntrukci. A proč? Protože může, je to undefined behavior, norma takové chování povoluje.
https://godbolt.org/z/bGqsEqsYc
Tohle je ještě dobrý. Crash by člověk tak nějak čekal.
Lepší je, když to přiřazení překladač využije jako informaci, že ten pointer nemůže být NULL.
A ještě lepší je čtení, které pak třeba může taky zmizet. :o
https://godbolt.org/z/qxKhYThY6
-
Na adresu 0 zapsat jde, pokud tam je mapována stránka.
Ještě na WinXP šlo namapovat nultou stránku z user space, ale vznikaly tím security díry, na nových widlích to jde jen z kernelu.
Třeba DOS emulátor potřebuje validní nultou adresu.
-
To, co tu tvrdíme už nějakou dobu. Nechat C jen tam, kde je to nezbytně nutné. A tam, kde chceš performance, ale není nutně důvod použít C, použít třeba Rust. Který dá +- podobný výkon, ale z principu vyloučí velmi časté chyby z C.
Proč lidi tolik trvají na tom, že i šroub musí zatlouct kladivem, jen proto, že je to o něco rychlejší, než najít ten šroubovák? :D
Ono psát nějaké datové struktury v Rustu je celkem chuťovka. Ve standardní knihovně se k tomu používá unsafe Rust a opět vám hrozí nedefinované chování kvůli aliasingu. Navíc řešíte řešíte problémy, které v C nejsou, jako například, jak vhodně použít PhantomData.
Protože ty problémy v C jsou. Schované. Rust Vás jen donutí je explicitně pojmenovat a vyřešit.
Když píšete vlastní datovou strukturu v Rustu stylem jako ve standardní knihovně, tak používate unsafe Rust a kompilátor vám s tím moc nepomůže (naopak tam jsou občas větší špeky než v C - to je aspoň můj subjektivní názor).
-
A jak zapíšu do registru který je na adrese 0x00000000
Nezapíšeš. Protože gcc ti na tohle, v závislosti na optimalizacích, verzi překladače a fázi měsíce vygeneruje ud2 isntrukci. A proč? Protože může, je to undefined behavior, norma takové chování povoluje.
https://godbolt.org/z/bGqsEqsYc
Tak překladač má být správně nastaven podle toho co dělám...
-O2 -fno-delete-null-pointer-checks
https://godbolt.org/z/TTfc87sxb (https://godbolt.org/z/TTfc87sxb)
test():
mov DWORD PTR ds:0, 1
ret
-
Proto vznikají články jako už zmíněný https://floooh.github.io/2018/06/17/handles-vs-pointers.html , proto máme valgrind (a taky glib nebo Qt/QML pro C++).
Tohle se může použít i v Rustu, když implementujete pool a chcete věděť jaké z hodnot jsou ještě použitelné a jaké ne. Protože třeba u pole či vektoru kompilátor Rustu neumí sledovat, jaká podmnožina indexů obsahuje platnou hodnotu, nebo kde se hodnota vyměnila.
-
Když píšete vlastní datovou strukturu v Rustu stylem jako ve standardní knihovně, tak používate unsafe Rust a kompilátor vám s tím moc nepomůže (naopak tam jsou občas větší špeky než v C - to je aspoň můj subjektivní názor).
Jen pokud nutně potřebujete speciální pointery.
On je trošku problém, že se v Rustu snaží hodně lidí psát jako by to bylo C (protože na něj z C přešli). Jsou případy, kdy to může být nutné, ale většina lidí nepíše nový typ datové struktury nebo nový async executor každý den.
(A)Rc a Weak by mělo pro většinu účelů s referencemi bohatě stačit.
Jo, pokud píšete no_std embedded kód bez heapu, tak to začíná být zajímavější. To přiznávám. Ale to bylo i to C.
Abychom se vrátili k tématu. C je těžké a občas dává programátorovi pocit, že to má pod kontrolou, i když něco přehlédl. Rust je těžký, protože za to přehlédnutí programátorovi vynadá. A oba jazyky dávají v "unsafe" režimu možnosti jak udělat něco nedovoleného.
Možná bychom to mohli zobecnit na.. programování je těžké a nemusíme si přidělávat práci ještě použitím nevhodného jazyka. Základy domu taky nekopu lžící a použiju bagr.
-
Na adresu 0 zapsat jde, pokud tam je mapována stránka.
V assembleru jo. Ale v C (bez nějakých nadstandardních flagů) to nesmíš. Takže ten vygenerovaný kód občas vypadá dost překvapivě.
-
No blbě. Pokud to vaše platforma potřebuje, tak vám musí dát nějaký nestandardní způsob jak to udělat. Standardní C to neumí (pokud teda NULL odpovídá adrese 0, to taky nemusí platit).
Nesmysl. Standardní C to umí a vždy se to dělalo přes pointer, pokud jste někdy viděl nějakou knihovnu pro mikrokontroléry, tak tam se nastavují registry takto běžně, třeba AVR.
Správný postup je napsat všechno bez chyb. Jenže my lidi to jaksi neumíme. A proto tu řešíme co mi jazyk udělá, když se seknu a něco neohandlím.
Tohle není chyba, tohle je zásadní neznalost že datový typ nemůže nabývat neomezeného počtu hodnot a má horní a spodní limit. A když má horní limit a přičítám číslo 1, tak zákonitě musím operaci povolit jen v rozmezí po horní limit - 1. To je realita s kterou musí pracovat každý programátor, který má fixní datové typy, toto neplatí pouze pro C, vychází to z toho že proměnná má určitou bitovou šířku a může mapovan omezený půočet hodnot. To je základní znalost v programování.
Je to jen další "wishful programming" kde si programátor přeje aby měl něco jiného než reálně má.
Jestli někdo ani toto nemá, tak ať se C raději vyhne a nechá pracovat ty kteří tomu rozumí a umí to napsat bez chyb.
-
V assembleru jo. Ale v C (bez nějakých nadstandardních flagů) to nesmíš. Takže ten vygenerovaný kód občas vypadá dost překvapivě.
A proč bych to jako nesměl? C není nijak limitované na prostředí kde je nějaký operační systém. V C lze psát jak high-level aplikace tak i low-level drivery které zapisují/nebo čtou na/z fixních adres. Takže nějakému "to nesmíš" se nezbývá než pozasmát. V embedded se takto programuje naprosto běžně...
V C lze psát jak portabilní kód, tak specifický kód pro nějakou architekturu. Podporuje i inline assember instrukce.
-
No blbě. Pokud to vaše platforma potřebuje, tak vám musí dát nějaký nestandardní způsob jak to udělat. Standardní C to neumí (pokud teda NULL odpovídá adrese 0, to taky nemusí platit).
Nesmysl. Standardní C to umí a vždy se to dělalo přes pointer, pokud jste někdy viděl nějakou knihovnu pro mikrokontroléry, tak tam se nastavují registry takto běžně, třeba AVR.
Fakt byste se měl podívat, co umí standardní C a co je nadstandardní rozšíření vašeho GCC dialektu.
Dost z toho, co jste tady napsal dělá jen GCC, protože to vůbec není součástí standardního C.
-
A jak zapíšu do registru který je na adrese 0x00000000
Nezapíšeš. Protože gcc ti na tohle, v závislosti na optimalizacích, verzi překladače a fázi měsíce vygeneruje ud2 isntrukci. A proč? Protože může, je to undefined behavior, norma takové chování povoluje.
https://godbolt.org/z/bGqsEqsYc
Tak překladač má být správně nastaven podle toho co dělám...
-O2 -fno-delete-null-pointer-checks
https://godbolt.org/z/TTfc87sxb (https://godbolt.org/z/TTfc87sxb)
test():
mov DWORD PTR ds:0, 1
ret
-fno-delete-null-pointer-checks je nestandardní gcc rozšíření. Otázka zněla, jak ve standardním C zapsat na adresu 0. Odpověd je, že to ve standardním C NELZE, protože dereference null pointeru je dle C standardu undefined behavior.
-
A jak zapíšu do registru který je na adrese 0x00000000
Nezapíšeš. Protože gcc ti na tohle, v závislosti na optimalizacích, verzi překladače a fázi měsíce vygeneruje ud2 isntrukci. A proč? Protože může, je to undefined behavior, norma takové chování povoluje.
https://godbolt.org/z/bGqsEqsYc
Tak překladač má být správně nastaven podle toho co dělám...
-O2 -fno-delete-null-pointer-checks
https://godbolt.org/z/TTfc87sxb (https://godbolt.org/z/TTfc87sxb)
test():
mov DWORD PTR ds:0, 1
ret
-fno-delete-null-pointer-checks je nestandardní gcc rozšíření. Otázka zněla, jak ve standardním C zapsat na adresu 0. Odpověd je, že to ve standardním C NELZE, protože dereference null pointeru je dle C standardu undefined behavior.
To nema s GCC nic společného. Takhle se psalo jestě než nějaký GCC vůbec existoval. A ani to nemá nutně nic společného s adresou 0. Prostě obecně když mám pointer a přectu přes něj nebo zapíšu, tak čtu nebo zapisuju z nějaké adresy v paměti která odpovídá hodnotě pointeru. Tady nic nestandardního není.
To proč je to undefined behaviour je, protože záleží na runtime environment co takový zápis nebo čtení způsobí. Na té adrese může být registr jehož vyčtením se vyclearujou flagy, nebo to může být obyčejná paměťová buňka. Ale to je undefined behaviour v obecné rovině. To vůbec neznamená že to nemůžu pro architecture (environment) specific kód použít a už vůbec to neznamená že by se nemělo na adresu zapsat nebo z ní přečíst když použiju pointer.
uint32_t *p_u32 = 0x00000040;
*p_u32 = 0x12345678; // zapiš hodnotu 0x12345678 na adresu 0x00000040
Toto je naprosto legitimní C-kód, sic platform specific.
-
uint32_t *p_u32 = 0x00000040;
*p_u32 = 0x12345678; // zapiš hodnotu 0x12345678 na adresu 0x00000040
Toto je naprosto legitimní C-kód, sic platform specific.
To není tak úplně pravda. Tohle je legální jen s "volatile". Jinak bude optimalizátor provádět psí kusy.
-
To nema s GCC nic společného. Takhle se psalo jestě než nějaký GCC vůbec existoval. A ani to nemá nutně nic společného s adresou 0. Prostě obecně když mám pointer a přectu přes něj nebo zapíšu, tak čtu nebo zapisuju z nějaké adresy v paměti která odpovídá hodnotě pointeru. Tady nic nestandardního není.
Pořád tomu nerozumíš. Nevadí, zkusím to vysvětlit znovu. null pointer (adresa 0) se v C standardu ošetřuje speciálně. C standard explicitně říká, že dereference null pointeru je undefined behavior. Tozn. pokud máš v C programu čtení nebo zápis adresy 0, překladač s tím může udělat cokoliv (a taky dělá). Může to ignorovat, může vygenerovat neplatnou instrukci, může to dokonce i fyzicky provést. Jakékoliv chování, včetně všech zmíněných, je při dereferenci null pointeru legální.
-
uint32_t *p_u32 = 0x00000040;
*p_u32 = 0x12345678; // zapiš hodnotu 0x12345678 na adresu 0x00000040
Toto je naprosto legitimní C-kód, sic platform specific.
To není tak úplně pravda. Tohle je legální jen s "volatile". Jinak bude optimalizátor provádět psí kusy.
Jasně, může. Ale o to mi zrovna nešlo. Šlo mi o to že v C je standard zapisovat přes pointer na paměťové adresy, nebo z nich číst. To že to může vyvolat něco co nechci, v závislosti na runtime environmentu je až druhotná věc.
Tady jsem ze srandy přeložil kód pro různé platformy, různými překladači a všechny zapíší na adresu 0x40 4 bajty s hodnotou 0x12 0x34 0x56 0x78. Takže jaképak jenom GCC, jakýpak nestandard?
Mimochodem, nepoužil jsem volatile, i když s ním je to jistota - to souhlas.
--- arm7 clang
f:
ldr r0, .LCPI0_0
mov r1, #64
str r0, [r1]
bx lr
.LCPI0_0:
.long 305419896
--- gcc
f():
mov DWORD PTR ds:64, 305419896
ret
--- djgpp
__Z1fv:
mov DWORD PTR ds:64, 305419896
ret
--- icc
f:
..B1.1: # Preds ..B1.0
mov DWORD PTR [64], 305419896 #6.6
ret #7.1
--- avr-gcc
f:
.L__stack_usage = 0
ldi r24,lo8(120)
ldi r25,lo8(86)
ldi r26,lo8(52)
ldi r27,lo8(18)
out 0x20,r24
out 0x21,r25
out 0x22,r26
out 0x23,r27
ret
-
To nema s GCC nic společného. Takhle se psalo jestě než nějaký GCC vůbec existoval. A ani to nemá nutně nic společného s adresou 0. Prostě obecně když mám pointer a přectu přes něj nebo zapíšu, tak čtu nebo zapisuju z nějaké adresy v paměti která odpovídá hodnotě pointeru. Tady nic nestandardního není.
Pořád tomu nerozumíš. Nevadí, zkusím to vysvětlit znovu. null pointer (adresa 0) se v C standardu ošetřuje speciálně. C standard explicitně říká, že dereference null pointeru je undefined behavior. Tozn. pokud máš v C programu čtení nebo zápis adresy 0, překladač s tím může udělat cokoliv (a taky dělá). Může to ignorovat, může vygenerovat neplatnou instrukci, může to dokonce i fyzicky provést. Jakékoliv chování, včetně všech zmíněných, je při dereferenci null pointeru legální.
Já tomu rozumím a chápu to. Já jenom nechápu co s tím máš za problém. Prostě si překladač nastavím jak potřebuju. Když píšu driver nebo knihovnu pro MCU kde tam potřebuju opravdu zapsat, tak si to tak nastavím. Když píšu program běžící v rámci OS tak to samozřejmě nechám generovat ud2 instrukci abych to odchytil v debuggeru. To že přes pointer se dá zapisovat na adresu a že toto je platné v C všude stále platí. Hodnota 0 může být speciálně handlovaná překladačem, ale to je optional. Tak samo ale může být potřeba vytvořit program který to cíleně dělá a překladač by to měl umožnit (alespoň nějakým přepínačem jako to má GCC).
-
Prostě si překladač nastavím jak potřebuju.
Můžeš prohlásit, že dereference pointeru není undefined behavior. Můžeš i zkusit donutit překladač, aby při dereferenci null pointeru dělal něco definovaného. Nemáš pak ale validní C program. Máš program, který je dle C standardu nefunkční, standard říká, že nedefinuje jeho chování.
-
Prostě si překladač nastavím jak potřebuju.
Můžeš prohlásit, že dereference pointeru není undefined behavior. Můžeš i zkusit donutit překladač, aby při dereferenci null pointeru dělal něco definovaného. Nemáš pak ale validní C program. Máš program, který je dle C standardu nefunkční, standard říká, že nedefinuje jeho chování.
To mě ale nezajímá, mě zajíma jaký bude strojový kód a co program dělá a jestli dělá to co chci.
-
To mě ale nezajímá, mě zajíma jaký bude strojový kód a co program dělá a jestli dělá to co chci.
V pořádku, ale neříkej potom, že píšeš v C. Nepíšeš totiž v C, ale v nějakém vrid dialektu, kdy jsou tvoje programy funkční jen s konkrétním nastavením překladače na konkrétní platformě.
-
To mě ale nezajímá, mě zajíma jaký bude strojový kód a co program dělá a jestli dělá to co chci.
V pořádku, ale neříkej potom, že píšeš v C. Nepíšeš totiž v C, ale v nějakém vrid dialektu, kdy jsou tvoje programy funkční jen s konkrétním nastavením překladače na konkrétní platformě.
Píšu v C. Kde mám platform/compiler specific věci, tak použiju #ifdef a do poslední #else větve dám error "not implemented" a nechám toho kdo to někdy bude v budoucnu potřebovat, aby ty #else větve dopsal. Nebudu paralyzovat celý vývoj jenom kvůli tomu abych všem linuxákům vyhověl. Za to mě zákazník neplatí.
-
Píšu v C. Kde mám platform/compiler specific věci, tak použiju #ifdef a do poslední #else větve dám error "not implemented" a nechám toho kdo to někdy bude v budoucnu potřebovat, aby ty #else větve dopsal. Nebudu paralyzovat celý vývoj jenom kvůli tomu abych všem linuxákům vyhověl. Za to mě zákazník neplatí.
Nepíšeš v C. Ve chvíli, kdy uděláš dereferenci null pointeru nebo jiné undefined behavior, tak od toho C standard dává ruce pryč. Na některých platformách takové věci můžou dávat smysl, ale vždy musíš explicitně zdůraznit, že se jedná o platformově závislou věc, kterou musíš speciálně řešit, např. nějakými flagy překladače. Ty to tady prezentuješ jako něco normálního, ale děláš je pravý opak, pohybuješ se mimo C stadard a musíš to záplatovat nějakými ohýbáky.
-
Píšu v C. Kde mám platform/compiler specific věci, tak použiju #ifdef a do poslední #else větve dám error "not implemented" a nechám toho kdo to někdy bude v budoucnu potřebovat, aby ty #else větve dopsal. Nebudu paralyzovat celý vývoj jenom kvůli tomu abych všem linuxákům vyhověl. Za to mě zákazník neplatí.
Nepíšeš v C. Ve chvíli, kdy uděláš dereferenci null pointeru nebo jiné undefined behavior, tak od toho C standard dává ruce pryč. Na některých platformách takové věci můžou dávat smysl, ale vždy musíš explicitně zdůraznit, že se jedná o platformově závislou věc, kterou musíš speciálně řešit, např. nějakými flagy překladače. Ty to tady prezentuješ jako něco normálního, ale děláš je pravý opak, pohybuješ se mimo C stadard a musíš to záplatovat nějakými ohýbáky.
Nesmysl. Zápis přes pointer na adresu v paměti je naprosto standardní věc v C. To jenom ty zase musíš vytahovat nějaké speciality jako adresa 0 abys ukázal že jsi věčný troublemaker. Good job :D
-
Ono existuje? UB se přece nikdy neděje. Ty jako programátor ses o to přece postaral a překladač ti v tom bezmezně věří. 8)
Občas mám pocit, že samotná slova jako "chování", "způsobuje" a podobné zastírají podstatu UB a proč je to občas takový mindfuck. Ta představa, že to UB něco dělá, je svým způsobem strašně špatně.
To už celkom preháňate, nemyslíte? Alebo je to tak, že by ste si to mali do študovať?
Proč přeháním? UB stojí na tom, že nesmí nastat. Validní C programy neobsahují UB. Programátor má za úkol zajistit aby k němu nemohlo dojít.
> Ono existuje?
Aha, takže tu na viac ako stovke príspevkov komunikujeme o niečom, čo neexistuje? To je naozaj zvláštne. UB je jednoducho formálny koncept a ako taký celkom určite existuje. Je to zjednodušujúci pojem používaný v odbornej komunikácii na popis toho, čo sa deje v programe, keď je jeho zdrojový kód mimo rámec definovaný sémantikou jazyka.
> UB se přece nikdy neděje.
Skúste vo svojej úvahe použiť definíciu nedefinovaného správania tak ako bola pôvodne myslená.
> Občas mám pocit, že samotná slova jako "chování", "způsobuje" a podobné zastírají podstatu UB
Ten pojem sa vám môže nepáčiť, môžete proti tomu protestovať, ale to je asi tak všetko, čo s tým môžete robiť. Odborná komunita sa jednoducho pred desiatkami rokov zhodla na tom, že sa tento pojem bude používať a tak sa používa. Paradoxne väčšina ľudí nechce v komunikácii uvádzať úplnú výstižnú definíciu a radšej používa zaužívaný zjednodušujúci pojem.
Mne osobne sa tiež nepáči slovo vlákno, ale chápem, že je ten pojem zaužívaný.
> mindfuck
??
-
Zápis přes pointer na adresu v paměti je naprosto standardní věc v C. To jenom ty zase musíš vytahovat nějaké speciality jako adresa 0 abys ukázal že jsi věčný troublemaker. Good job :D
Já jen cituji C standard, kde je explicitně uvedeno, že dereference null pointeru je undefined behavior. Rozumím tomu, že C standard nemáš přečtený a nevíš to. Nerozumím ale tomu, proč si pořád v opozici a snažíš se tvrdit, že zápis na jakoukoliv adresu přes pointer je v pohodě? Není to v pohodě, zápis na adresu 0 je dle C standardu undefined behavior. Ve chvíli, kdy to uděláš, tak od toho C standard dává ruce pryč a takový program může dělat cokoliv.
-
> Ono existuje?
Aha, takže tu na viac ako stovke príspevkov komunikujeme o niečom, čo neexistuje? To je naozaj zvláštne. UB je jednoducho formálny koncept a ako taký celkom určite existuje. Je to zjednodušujúci pojem používaný v odbornej komunikácii na popis toho, čo sa deje v programe, keď je jeho zdrojový kód mimo rámec definovaný sémantikou jazyka.
> UB se přece nikdy neděje.
Skúste vo svojej úvahe použiť definíciu nedefinovaného správania tak ako bola pôvodne myslená.
> Občas mám pocit, že samotná slova jako "chování", "způsobuje" a podobné zastírají podstatu UB
Ten pojem sa vám môže nepáčiť, môžete proti tomu protestovať, ale to je asi tak všetko, čo s tým môžete robiť. Odborná komunita sa jednoducho pred desiatkami rokov zhodla na tom, že sa tento pojem bude používať a tak sa používa. Paradoxne väčšina ľudí nechce v komunikácii uvádzať úplnú výstižnú definíciu a radšej používa zaužívaný zjednodušujúci pojem.
Mne osobne sa tiež nepáči slovo vlákno, ale chápem, že je ten pojem zaužívaný.
Jo, vyjádřil jsem se asi dost nešikovně. Chtěl jsem zdůraznit že zákeřnost UB je v tom, že to není jen nějaká implementačně závislá akce, která se provede místo toho UB.
Že UB znamená, že tahle situace ve validním programu nesmí nastat. A překladač na tom staví a propaguje to i na okolní kód. Takže vygenerovaný kód s tímhle stavem vůbec nepočítá a můžou se dít fakt divné věci.
> mindfuck
??
Zažil jsem fakt divoké věci. Co se dělo vůbec nepřipomínalo zdroják a navíc to divné chování nebylo vůbec omezení na místo toho UB.
-
Zápis přes pointer na adresu v paměti je naprosto standardní věc v C. To jenom ty zase musíš vytahovat nějaké speciality jako adresa 0 abys ukázal že jsi věčný troublemaker. Good job :D
Já jen cituji C standard, kde je explicitně uvedeno, že dereference null pointeru je undefined behavior. Rozumím tomu, že C standard nemáš přečtený a nevíš to. Nerozumím ale tomu, proč si pořád v opozici a snažíš se tvrdit, že zápis na jakoukoliv adresu přes pointer je v pohodě? Není to v pohodě, zápis na adresu 0 je dle C standardu undefined behavior. Ve chvíli, kdy to uděláš, tak od toho C standard dává ruce pryč a takový program může dělat cokoliv.
Já když říkám že píšu v C, tak mluvím o syntaxi jazyka. Že ty v tom vidíš standard a nedokázal bys napsat driver který musí zapsat na adresu 0 protože bys měl panickou hrůzu že to je undefined behaviour, to je tvoje mínus. Já jsem pragmatik a flexi. Kde to jde tak píšu portabilní kód, kde to nejde tak platform specific. A jestli tomu říkáš že nepíšu v C, tak si tomu tak říkej, je mě to celkem šumák. Asi vlastně nemám dále co bych tady k diskuzi přispěl, jsem diskvalifikován se o C vůbec bavit, protože dělám embedded věci. Oukej.
-
Já když říkám že píšu v C, tak mluvím o syntaxi jazyka. Že ty v tom vidíš standard a nedokázal bys napsat driver který musí zapsat na adresu 0 protože bys měl panickou hrůzu že to je undefined behaviour, to je tvoje mínus. Já jsem pragmatik a flexi. Kde to jde tak píšu portabilní kód, kde to nejde tak platform specific. A jestli tomu říkáš že nepíšu v C, tak si tomu tak říkej, je mě to celkem šumák. Asi vlastně nemám dále co bych tady k diskuzi přispěl, jsem diskvalifikován se o C vůbec bavit, protože dělám embedded věci. Oukej.
Ale to se týká i embedded. I v embedded musíš řešit, že je dereference null pointeru undefined behavior. Dám ti praktický příklad. Jsou mikrokontroléry, které mají RAM v paměťové mapě od adresy 0, takže adresa 0 je naprosto validní pointer do RAM. Co s tím? Vyřeší se to tak, že se RAM zadefinuje od adresy 1 a adresa 0 se ignoruje, abychom se vyhnuli problémům s dereferencí null pointeru. Přijdeme o jeden bajt RAM na mikrokontroléru, ale to je nízká cena za vyřešení problémů s adresou 0 a undefined behavior.
-
Já když říkám že píšu v C, tak mluvím o syntaxi jazyka. Že ty v tom vidíš standard a nedokázal bys napsat driver který musí zapsat na adresu 0 protože bys měl panickou hrůzu že to je undefined behaviour, to je tvoje mínus. Já jsem pragmatik a flexi. Kde to jde tak píšu portabilní kód, kde to nejde tak platform specific. A jestli tomu říkáš že nepíšu v C, tak si tomu tak říkej, je mě to celkem šumák. Asi vlastně nemám dále co bych tady k diskuzi přispěl, jsem diskvalifikován se o C vůbec bavit, protože dělám embedded věci. Oukej.
Ale to se týká i embedded. I v embedded musíš řešit, že je dereference null pointeru undefined behavior. Dám ti praktický příklad. Jsou mikrokontroléry, které mají RAM v paměťové mapě od adresy 0, takže adresa 0 je naprosto validní pointer do RAM. Co s tím? Vyřeší se to tak, že se RAM zadefinuje od adresy 1 a adresa 0 se ignoruje, abychom se vyhnuli problémům s dereferencí null pointeru. Přijdeme o jeden bajt RAM na mikrokontroléru, ale to je nízká cena za vyřešení problémů s adresou 0 a undefined behavior.
Jenže na té adrese může být registr která má nenahraditelnou speciální funkci či význam. To je naprostý nesmysl to řešit tak jak říkáš. To může udělat jenom teoretik který k tomu prakticky nikdy nečmuch.
Normálně se otestuje překladač zda to podporuje a když ne, tak se vybere jiný. Takhle se to v praxi řeší.
Případně se použije inline assembler.
Těžko očekávat že nejaký kód handlující absolutní adresy registrů se bude někam přenášet. Proto je undefined behaviour úplně jedno.
-
Jenže na té adrese může být registr která má nenahraditelnou speciální funkci či význam. To je naprostý nesmysl to řešit tak jak říkáš. To může udělat jenom teoretik který k tomu prakticky nikdy nečmuch.
To je velmi neobvyklé, že by byl speciální registr nějakého mikrokontroléru na adrese 0, kdy jsi na to prakticky narazil a musel jsi to řešit? Neříkam, že neexistují architektury, kde to tak je, ale zajímalo by mě, kde jsi to prakticky potkal? Pokud se totiž něco takového stane, máš opravdu problém. Buď musíš do assembleru a provést zápis tam, nebo dělat v C něco hodně nestandarního a doufat, že se to nerozbije s novou verzí překladače.
-
Vědět, že je operátor >> implementation defined (většina lidí to neví a předpokládá, že se jedná o aritmetický shift).
Pak by si asi měli přečíst dokumentaci. Stejně tak by někdo mohl říct, že v Rustu většina lidí neví, že chování + závisí na tom, jestli kompilujete Debug nebo Release. Nebo v C#, že se chování ToUpper a ToLower řídí aktuálním locale a někdy můžete dostat fakt nečekané výsledky. Nebo v Gleamu, dělení 0 vrací 0, není to chyba. A takových situací jsou hromady ať už jste v Rustu, C, C++, Go nebo F#. Je lepší nepředpokládat a přečíst si dokumentaci.
Přesně tak. Znalost jazyka je nutná pro každý jazyk. A řeči typu "jazyk neděla co chci" = jen lenost si ho nastudovat. Každý jazyk se musí nastudovat, i ty moderní. Jestli to je někomu bližší, a snažší uchopit, tak budiž, ale ve výsledku je potřeba to nastudovat a tomu se člověk nevyhne ani v tom Rustu, ani C#. Každý jazyk má svá specifika a toto jsou dobré příklady že i v jiných jazycích se lze lehce střelit do nohy.
Já nemám problém si něco nastudovat. Ale mám problém když jazyk říká "ano, o to se ti postarám, někdy, možná". Tím spíše, že C měl být "přenositelný assembler". No, nepovedlo, se nevadí, jedeme dál.
-
Dále nechápu to s tím assemblerem. C přesně odpovídá tomu jak jsou věci v assembleru. Je to de-facto portabilní assembler. A právě ten kdo prozkoumá věci v C až na úroveň asembleru, tak ten má pak v těch věcech jasno a žádná mentální zátěž to pro něj už není. Mentální zátěž je, když v tom jasno právě nemá a musí to pořád vyhodnocovat.
No právě že assembleru neodpovídá (viz shiftování signed intu). Tím, že se snaží být ne jeden assembler, ale všechny assemblery najednou (s tím, že každý se vlastně chová trochu jinak), tak se z něj stává guláš, kde spousta věcí musí fungovat polovičatě, jako v každé abstrakci.
Takže pokud prozkoumáš C až na úroveň assembleru, tak podle standardu C ti to je k ničemu, protože stejný kód se ti na procesoru xyz bude chovat úplně jinak.
-
Hosi, nepresvedcili jste me. To ze je C nizkourovnovy jazyk, je jasne. Ale proc bych mel psat
a << -1 To je jasny, ze je to undefined behaviour.
Ale porad si myslim, ze to neni nijak slozity jazyk, pouze jsou v nem zaludnosti.
Pokud by C byl dokonala abstrakce nad CPU, tak by se v nem nedal psat operacni system, drivery, embedded kod. Nebo mi chcete rict, ze v Haskellu jde napsat operacni system?
-
Hosi, nepresvedcili jste me. To ze je C nizkourovnovy jazyk, je jasne. Ale proc bych mel psat
a << -1 To je jasny, ze je to undefined behaviour.
O takovém shiftu tady celou dobu nebyla řeč. Mluvilo se o :
a >> 1
kde a je signed integer.
-
Hosi, nepresvedcili jste me. To ze je C nizkourovnovy jazyk, je jasne. Ale proc bych mel psat
a << -1 To je jasny, ze je to undefined behaviour.
Ale porad si myslim, ze to neni nijak slozity jazyk, pouze jsou v nem zaludnosti.
Pokud by C byl dokonala abstrakce nad CPU, tak by se v nem nedal psat operacni system, drivery, embedded kod. Nebo mi chcete rict, ze v Haskellu jde napsat operacni system?
Operační systém v Haskellu (https://github.com/dls/house)
Tak on je rozdíl mezi tím, když musíš psát inline assembler a mezi tím, když si musíš pamatovat, že toto se sice tváří, jako že ok, ale ok to není.
-
BTW, ještě se tu neobjevil Preprocessor iceberg :
https://jadlevesque.github.io/PPMP-Iceberg/
-
Ale porad si myslim, ze to neni nijak slozity jazyk, pouze jsou v nem zaludnosti.
C je syntakticky jednoduchý jazyk. Ale není jednoduchý na použití.
V porovnání s veškerou konkurencí kromě čistého ASM mu chybí jakékoliv pomůcky pro efektivní práci. Není skoro žádná standardní knihovna. Člověk si to prostě musí odedřít s malloc/free, pointery a polem. A pořád u toho dávat pozor.
Někdy je to nutné, ale většinou ne.
-
Jenže na té adrese může být registr která má nenahraditelnou speciální funkci či význam. To je naprostý nesmysl to řešit tak jak říkáš. To může udělat jenom teoretik který k tomu prakticky nikdy nečmuch.
To je velmi neobvyklé, že by byl speciální registr nějakého mikrokontroléru na adrese 0, kdy jsi na to prakticky narazil a musel jsi to řešit? Neříkam, že neexistují architektury, kde to tak je, ale zajímalo by mě, kde jsi to prakticky potkal? Pokud se totiž něco takového stane, máš opravdu problém. Buď musíš do assembleru a provést zápis tam, nebo dělat v C něco hodně nestandarního a doufat, že se to nerozbije s novou verzí překladače.
Je ale velmi obvyklé, že procesor po resetu začne vykonávat program od adresy 0. Takže zápisem instrukce skoku na adresu 0 de facto měním vektor "přerušení" typu reset.
-
Co s tím? Vyřeší se to tak, že se RAM zadefinuje od adresy 1 a adresa 0 se ignoruje, abychom se vyhnuli problémům s dereferencí null pointeru. Přijdeme o jeden bajt RAM na mikrokontroléru, ale to je nízká cena za vyřešení problémů s adresou 0 a undefined behavior.
Jo, no naprosto geniální tah. Třeba v případě, že takovej Atmel tam má namapovanej jeden registr je to dobrý fundamentalistický přístup do něj prostě nešahat...
-
Co s tím? Vyřeší se to tak, že se RAM zadefinuje od adresy 1 a adresa 0 se ignoruje, abychom se vyhnuli problémům s dereferencí null pointeru. Přijdeme o jeden bajt RAM na mikrokontroléru, ale to je nízká cena za vyřešení problémů s adresou 0 a undefined behavior.
Jo, no naprosto geniální tah. Třeba v případě, že takovej Atmel tam má namapovanej jeden registr je to dobrý fundamentalistický přístup do něj prostě nešahat...
Pragmatický přístup je samozřejmě ten, že se pro některé platformy to nedefinované chování dodefinuje. Přece jenom standard C od tohohle dává ruce pryč a dovoluje naprosto cokoliv.
A důsledek je samozřejmě ten, že něco co embedáci u sebe normálně dělají, je na jiné platfomě recept na katastrofu. Může nastat něco co ani nepřipomíná předpokládaný segfault při přístupu na adresu 0.
-
Pragmatický přístup je samozřejmě ten, že se pro některé platformy to nedefinované chování dodefinuje. Přece jenom standard C od tohohle dává ruce pryč a dovoluje naprosto cokoliv.
Presne tak bych to bral taky. Standard se snazi byt univerzalni, coz je dobre, tak rika, ze dane chovani nedefinuje, coz je taky dobre, a od programatora se ceka, ze to nebude v prenositelnem kodu delat.
Pokud pro konkretni platformu dava konkretni chovani smysl, nebo je dokonce potreba (viz nejen ten Atmel), necht je to platform specific chovani. Stejne jako treba nepouzivat __asm__ __volatile__ ("cli") jen proto, ze to neni kosher protoze to neni v normach, a nadavat co vsechno se s Arduinem neda delat by bylo trochu ... divne.
-
V porovnání s veškerou konkurencí kromě čistého ASM mu chybí jakékoliv pomůcky pro efektivní práci. Není skoro žádná standardní knihovna. Člověk si to prostě musí odedřít s malloc/free, pointery a polem. A pořád u toho dávat pozor.
Někdy je to nutné, ale většinou ne.
Dá se to brát tak, že ti kdo v tom programují si staví vlastní knihovnu podle sebe. Například někteří lidé ani nepoužívají malloc a píší si vlastní alokátory. Naopak už se mi několikrát stalo v jiných jazycích se standardní knihovnou, že se v ní něco změnilo a já pak strávil týden přepisováním kódu, aby ho vůbec bylo možné přeložit s novou verzí.
-
Co s tím? Vyřeší se to tak, že se RAM zadefinuje od adresy 1 a adresa 0 se ignoruje, abychom se vyhnuli problémům s dereferencí null pointeru. Přijdeme o jeden bajt RAM na mikrokontroléru, ale to je nízká cena za vyřešení problémů s adresou 0 a undefined behavior.
Jo, no naprosto geniální tah. Třeba v případě, že takovej Atmel tam má namapovanej jeden registr je to dobrý fundamentalistický přístup do něj prostě nešahat...
Pragmatický přístup je samozřejmě ten, že se pro některé platformy to nedefinované chování dodefinuje. Přece jenom standard C od tohohle dává ruce pryč a dovoluje naprosto cokoliv.
Dodefinování zjednoduší ladění, protože se kompilátor bude chovat podle známých pravidel, ale stejně to nepředejde situacím, které programátor neočekával (pokud například čeká, že po přičtení 1 bude číslo větší, tak tahle vlastnost tu není). Podle mě by bylo lepší, kdyby takovéhle věci vyhazovaly chybu, což bohužel řada jazyků nedělá.
-
Raději nedefinované chování, než za každou cenu vymýšlet konkrétní chování, jež nemusí být intuitivní. Nevím, jak je to ve standardu dnes, ale další operace, jež by měly být IMHO nedefinované: umocňování záporného čísla na racionální, modulo dělení záporných čísel...
-
Raději nedefinované chování, než za každou cenu vymýšlet konkrétní chování, jež nemusí být intuitivní.
Tady nastává jeden praktický problém. Konkrétní chování programu, když to UB přece jenom nastane, dokáže tu neintuitivnost dotáhnout do netušených extrémů.
UB umožňuje autorům překladače doplnit tam nějaké příčetné chování. Ale když to neudělají, tak je to strašná past.
-
Nedefinované chování umožňuje primitivní operace pokrýt jednou CPU intrukcí. Když bude definované všechno, na každou prkotinu bude knihovní funkce, která bude emulovat operátor přesně každý bit výsledku.
Teď jde použít oboje - rychlý operátor s nejistým chováním, nebo si zavolat funkci a mít výsledek pomalu, ale precizně.
-
Nedefinované chování umožňuje primitivní operace pokrýt jednou CPU intrukcí. Když bude definované všechno, na každou prkotinu bude knihovní funkce, která bude emulovat operátor přesně každý bit výsledku.
Teď jde použít oboje - rychlý operátor s nejistým chováním, nebo si zavolat funkci a mít výsledek pomalu, ale precizně.
Na tohle by bohatě stačilo implementačně definované chování (jako je třeba u signed dělení). Rozdíl oproti UB je velký :
- To chování se vždycky stane. Překladač nemůže předpokládat, že daná situace ve validním kódu nesmí nastat. Takže opravdu vygeneruje odpovídající CPU instrukce.
- Překladač/platforma musí zdokumentovat, jak se ta operace bude chovat.
Ve výsledku mám jednu CPU instrukci a žádné démony z nosa. :)
-
https://wordsandbuttons.online/so_you_think_you_know_c.html
-
https://wordsandbuttons.online/so_you_think_you_know_c.html
5/5
Nikdy o sobě neřeknu, že dokonale ovládám češtinu, třebaže ji používám celý život. Ale myslím, že ji ovládám velmi obstojně, rozhodně dostatečně, abych v ní mohl vyjádřit jakoukoli svou myšlenku. Takhle přistupuji i k programovacím jazykům. Ve sveřepých šakalech chybu neudělám, ale jistě by se našel jiný špek, na který bych se nachytal. Jenže to, podle mě, není podstatné. O jazyku samotném to nic nevypovídá. Ukažte mi programovací jazyk, v němž nenajdete konstrukci, která by se dala považovat za špek. Někdo považuje vyjmenovaná slova za špek, někdo shodu podmětu s přísudkem. Ve skutečnosti to není nic komplikovaného na používání - s trochou cviku. Opravdovým špekům se lze i při pokročilém používání vyhnout.
Např. mnohými tolik opěvovaný Rust. Na mě působí dojmem, že přechodníky nemá, protože v nich spousta lidí dělá chyby, tak mě nutí místo nich použít nějaký krkolomný konstrukt.
-
Např. mnohými tolik opěvovaný Rust. Na mě působí dojmem, že přechodníky nemá, protože v nich spousta lidí dělá chyby, tak mě nutí místo nich použít nějaký krkolomný konstrukt.
Naopak. Rust má spoustu možností, jak cokoli naprasit jakkoli - počínaje inline asm, přes "normální" unsafe Rust až po FFI. Kromě toho nabízí spoustu abstrakcí a pohodlných způsobů, jak se v normálním kódu vyvarovat různých problémů s pomocí jazyka. Na rozdíl třeba od C, které má jenom režim "pras jak umíš".
-
Na rozdíl třeba od C, které má jenom režim "pras jak umíš".
Při práci v C taky fungujou příčetnějí režimy, které krotí ty nejprasáčtějí praktiky. Jen nejsou nijak vynucené jazykem, takže to hlídají lintery, code review a podobně.
-
Na rozdíl třeba od C, které má jenom režim "pras jak umíš".
Při práci v C taky fungujou příčetnějí režimy, které krotí ty nejprasáčtějí praktiky. Jen nejsou nijak vynucené jazykem, takže to hlídají lintery, code review a podobně.
Code review je ultimátní řešení, jenže má dvě nevýhody:
1. Není podpořené jazykem
2. Je opět závislé na lidském faktoru
Všechny ty bezpečnostní problémy v OpenSSL a jinde existovaly i přes možnost review člověkem a existenci linterů.
-
Na rozdíl třeba od C, které má jenom režim "pras jak umíš".
Při práci v C taky fungujou příčetnějí režimy, které krotí ty nejprasáčtějí praktiky. Jen nejsou nijak vynucené jazykem, takže to hlídají lintery, code review a podobně.
Jde o směr uvažování:
V C máte by default všechno nebezpečné, a snažíte se to nějak svázat.
V Rustu máte by default všechno bezpečné 1), a když potřebujete, můžete jít do rizika.
A v tomto uvažování se pak ty jazyky dělí. Mám pocit, že právě Zig jde spíše tou C cestou, zatímco třeba já nevím, třeba Haskell jdou spíše tou druhou.
1) Samozřejmě v rámci možností. Aby tu zase někdo nevytahoval, že v Rustu jde vytvořit špatný kód.
-
Na rozdíl třeba od C, které má jenom režim "pras jak umíš".
Při práci v C taky fungujou příčetnějí režimy, které krotí ty nejprasáčtějí praktiky. Jen nejsou nijak vynucené jazykem, takže to hlídají lintery, code review a podobně.
O to právě jde. Pokud můžu nějaký problém vyloučit, nebo zaručit výrazně menší dopad průšvihu tím, že to nechám na jazyku a překladači, tak proč přidávat práci reviewerovi? V C je skoro jakákoliv operace s pointerem nebo polem potenciální zdroj průšvihu, a člověk při review musí hlídat všechno. V jiných jazycích vím, že spoustu těchto rizik zabrání struktura jazyka/překladač, a když narazím na nějaký unsafe blok, tak vím, že si v něm musím dávat pořádný pozor - ten pozor, co v C potřebuji u každého řádku celého programu.
-
V jiných jazycích vím, že spoustu těchto rizik zabrání struktura jazyka/překladač, a když narazím na nějaký unsafe blok, tak vím, že si v něm musím dávat pořádný pozor - ten pozor, co v C potřebuji u každého řádku celého programu.
Nepříjemné je, že ten unsafe kód může záviset i na invariantech ze safe kódu (např. index do pole může spočítat safe kód), takže musíte dávat i pozor na safe kód. Respektive i změna v safe kódu pak může rozbít program.
Jinak samozřejmě, pokud ten unsafe kód napsal někdo, komu bezmezně věříte, že to udělal dobře a vystavil vám bezpečné safe rozhraní, tak jste v pohodě. Pokud ovšem ten unsafe kód píšete sám, tak se dostáváte do podobné situace jako v C s tím, že psaní unsafe kódu v nějakém bezpečném jazyce může být složitější než psaní C.
Např. moje osobní zkušenost je, že B-strom, jak je napsaný v unsafe kódu ve standardní knihovně Rustu v souboru node.rs, bych napsat nedokázal, ale v C-like jazyce ho napsat dokážu. Rozdíl je v tom, že v Rustu si člověk musí pohlídat mnohem víc invariantů než v C a kód je tam také kvůli tomu cca 2-3 delší než v C-like jazyce.
-
V jiných jazycích vím, že spoustu těchto rizik zabrání struktura jazyka/překladač, a když narazím na nějaký unsafe blok, tak vím, že si v něm musím dávat pořádný pozor - ten pozor, co v C potřebuji u každého řádku celého programu.
Nepříjemné je, že ten unsafe kód může záviset i na invariantech ze safe kódu (např. index do pole může spočítat safe kód), takže musíte dávat i pozor na safe kód. Respektive i změna v safe kódu pak může rozbít program.
Neptal jsem se posledně na nějakou ukázku? Bohužel si nevybavuji výsledek.
Rozdíl je v tom, že v Rustu si člověk musí pohlídat mnohem víc invariantů než v C
To mi nějak matematicky nevychází.
Logika vytvoří stejné množství stavů. Rust s unsafe ti část pohlídá, C ti nepohlídá žádné.
-
Nepříjemné je, že ten unsafe kód může záviset i na invariantech ze safe kódu (např. index do pole může spočítat safe kód), takže musíte dávat i pozor na safe kód. Respektive i změna v safe kódu pak může rozbít program.
Neptal jsem se posledně na nějakou ukázku? Bohužel si nevybavuji výsledek.
Příklad je na stránce Working with Unsafe (https://doc.rust-lang.org/nomicon/working-with-unsafe.html), před odstavcem
This program is now unsound, Safe Rust can cause Undefined Behavior, and yet we only modified safe code. This is the fundamental problem of safety: it's non-local. The soundness of our unsafe operations necessarily depends on the state established by otherwise "safe" operations.
Rozdíl je v tom, že v Rustu si člověk musí pohlídat mnohem víc invariantů než v C
To mi nějak matematicky nevychází.
Logika vytvoří stejné množství stavů. Rust s unsafe ti část pohlídá, C ti nepohlídá žádné.
Mé tvrzení se týká jen toho B-stromu, konkrétně jen node.rs (https://github.com/rust-lang/rust/blob/b87eda7fdf8034c52b3abef52b443b8573484eda/library/alloc/src/collections/btree/node.rs) (nechci se pouštět do obecných tvrzení). Implementace Rustu má kvůli borrow checkeru spoustu zbytečných funkcí, které v C nepotřebuji - např. tyhle
impl<K, V, Type> NodeRef<marker::Owned, K, V, Type> {
/// Mutably borrows the owned root node. Unlike `reborrow_mut`, this is safe
/// because the return value cannot be used to destroy the root, and there
/// cannot be other references to the tree.
pub(super) fn borrow_mut(&mut self) -> NodeRef<marker::Mut<'_>, K, V, Type> {
NodeRef { height: self.height, node: self.node, _marker: PhantomData }
}
/// Slightly mutably borrows the owned root node.
pub(super) fn borrow_valmut(&mut self) -> NodeRef<marker::ValMut<'_>, K, V, Type> {
NodeRef { height: self.height, node: self.node, _marker: PhantomData }
}
/// Irreversibly transitions to a reference that permits traversal and offers
/// destructive methods and little else.
pub(super) fn into_dying(self) -> NodeRef<marker::Dying, K, V, Type> {
NodeRef { height: self.height, node: self.node, _marker: PhantomData }
}
}
To jsou funkce, které se samotným B-stromem nijak nesouvisí, takže je v jiných jazycích nepotřebuji.
-
Mé tvrzení se týká jen toho B-stromu, konkrétně jen node.rs (https://github.com/rust-lang/rust/blob/b87eda7fdf8034c52b3abef52b443b8573484eda/library/alloc/src/collections/btree/node.rs) (nechci se pouštět do obecných tvrzení). Implementace Rustu má kvůli borrow checkeru spoustu zbytečných funkcí, které v C nepotřebuji - např. tyhle
impl<K, V, Type> NodeRef<marker::Owned, K, V, Type> {
/// Mutably borrows the owned root node. Unlike `reborrow_mut`, this is safe
/// because the return value cannot be used to destroy the root, and there
/// cannot be other references to the tree.
pub(super) fn borrow_mut(&mut self) -> NodeRef<marker::Mut<'_>, K, V, Type> {
NodeRef { height: self.height, node: self.node, _marker: PhantomData }
}
/// Slightly mutably borrows the owned root node.
pub(super) fn borrow_valmut(&mut self) -> NodeRef<marker::ValMut<'_>, K, V, Type> {
NodeRef { height: self.height, node: self.node, _marker: PhantomData }
}
/// Irreversibly transitions to a reference that permits traversal and offers
/// destructive methods and little else.
pub(super) fn into_dying(self) -> NodeRef<marker::Dying, K, V, Type> {
NodeRef { height: self.height, node: self.node, _marker: PhantomData }
}
}
To jsou funkce, které se samotným B-stromem nijak nesouvisí, takže je v jiných jazycích nepotřebuji.
Troufám si tvrdit, že něco podobného běžný programátor v Rustu neimplementuje. Je jistě možné se točit na jedné okrajové situaci, ale pak se dostáváme k banálnímu přístupu obhájců C - můžeme si to všechno udělat jednoduše a tady to zrovna vypadá, že se to vyplatí.
-
Nepříjemné je, že ten unsafe kód může záviset i na invariantech ze safe kódu (např. index do pole může spočítat safe kód), takže musíte dávat i pozor na safe kód. Respektive i změna v safe kódu pak může rozbít program.
Neptal jsem se posledně na nějakou ukázku? Bohužel si nevybavuji výsledek.
Příklad je na stránce Working with Unsafe (https://doc.rust-lang.org/nomicon/working-with-unsafe.html), před odstavcem
This program is now unsound, Safe Rust can cause Undefined Behavior, and yet we only modified safe code. This is the fundamental problem of safety: it's non-local. The soundness of our unsafe operations necessarily depends on the state established by otherwise "safe" operations.
Děkuji za příspěvek.
Viděl bych tam následující problém:
fn index(idx: usize, arr: &[u8]) -> Option<u8> {
if idx <= arr.len() {
unsafe {
Some(*arr.get_unchecked(idx))
}
} else {
None
}
}
V tom unsafe kódu pracuji s proměnnou (idx), která má špatný rozsah. Není nijak směrodatné, že kontroluju rozsah v safe části. (Naopak, je to rozumné, protože to bude v tomto případě jednodužší.) Ta kontrola je chybná, a díky tomu je chybné i chování unsafe části. Vstoupil jsem do unsafe části, čímž jsem si vypnul (některé) kontroly, a celkem jasně tam mám proměnnou, kterou si táhnu z vnějšího safe kontextu, a mám na něj nějaké předpoklady, tak bych si ty předpoklady měl jako programátor ověřit. Neudělal jsem to, chyba.
V tom druhém případě je to podobný princip.
Úplně v tom nevidím tu katastrofu, kterou v tom pozoruješ ty. Hmm.
V každém případě ta matematika nevychází.
Mám-li v kódu jednu chybu, tak mám v kódu jednu chybu. Je dost dobře jedno, jestli se projeví tady nebo tam. A ano, může se množit. Ale stále mám v kódu jednu chybu. Skutečnost, že se chyba projeví v safe Rust neznamená, že ta chyba je v této části. Opravovat ji musím tam, kde je. Ano, může to být složitější. Tak on Rust prostě je složitější.
Pokud přijmu tvé tvrzení, že unsafe Rust vyžaduje cca třikrát tolik kódu jak C, tak jakmile napíšu kód, kde bude poměr větší jak 3:1 Safe versus Unsafe, už jsem na tom lépe jak čistý C kód :D
Ano, psaní unsafe je třeba věnovat péči, ale nevidím v tom důvod přecházet na jazyk z kategorie unsafe by default. I kdyby ta péče měla být třikrát tak větší.