Tohle nahrazuje drobny problem prusvihem, protoze ta prvni varianta funguje i pri zmene typu a, b na jiny (vetsi, mensi). Ta druha pouzitim UINT_MAX predpoklada unsigned int a to nemusi byt pravda (je snadne to pri zmene kodu prehlednout)
On zasadni problem je, ze asi v zadnem beznem jazyku (*) neni konstrukce, jak mu rici, ze to preteka a vim o tom a jt o v poradku. Napriklad v obsluze HW, je naprosto bezne, ze hodnota preteka. Citace udalosti, casovace a spooousta dalsich prikladu.
(*) mozna ze ne v kazdem, ale zadny me nenapada
Programovací jazyk Ada má modulární typy, které realizují modulární aritmetiku. Tam přetečení nehrozí, protože jakákoliv operace vždycky skončí v povoleném rozsahu.
U standardních typů není přetečení povoleno a je signalizováno výjimkou.
Mějme ukázkový program:
with Ada.Text_IO; use Ada.Text_IO; procedure Overflow_Example is subtype Small_Int is Integer range 1 .. 10; X : Small_Int := 10; begin Put_Line("Initial value of X: " & Integer'Image(X)); X := X + 1; Put_Line("New value of X: " & Integer'Image(X)); exception when Constraint_Error => Put_Line("Constraint_Error: Integer range overflow detected."); end Overflow_Example;
Tento program při svém spuštění vypíše následující:
Initial value of X: 10 Constraint_Error: Integer range overflow detected.
Oproti tomu mírně upravený program s využitím modulárního typu:
with Ada.Text_IO; use Ada.Text_IO; procedure Modular_Example is type Modular_Int is mod 10; X : Modular_Int := 9; begin Put_Line("Initial value of X: " & Modular_Int'Image(X)); X := X + 1; Put_Line("New value of X: " & Modular_Int'Image(X)); exception when Constraint_Error => Put_Line("Constraint_Error: Integer range overflow detected."); end Modular_Example;
vypíše toto:
Initial value of X: 9 New value of X: 0
Problém s ftrapv a fwrapv je, že je to globální nastavení. Ale rozhodnutí jestli má smysl aby nějaký výpočet přetekl je lokální jen pro ten výpočet.
Myslím že pro drtivou většinu intových výpočtů je jakékoliv přetečení hausnumero a důsledek nějaké chyby. Wrap má smysl jen ve výjimečných případech. Takže by to chtělo možnost abych jako programátor mohl explicitně říct, že tady s tím přetečením počítám.
> Mně přijde, že to explicitně říkáš tím, že použiješ jediný jazyk, který oficiálně počítá s přetékáním.
O kterém jazyce to mluvíš? Wrap na intech jsem potkal ve spoustě jazyků. Naproti tomu v C to oficiálně nesmíš dopustit, aspoň teda na signed intech. Overflow je nedefinované chování.
BTW, s čím nějaký jazyk oficiálně počítá je ještě o parník globálnější stav, něž jakýkoliv flag překladače.
14. 5. 2024, 15:06 editováno autorem komentáře
C není nadstavba nad assemblerem, takže je úplně jedno, co se v assembleru normálně používá.
Přetečení v C je nedefinovaná operace a udělat blbé add with carry vyžaduje nadstandardní rozšíření od překladače(jako __builtin_add_overflow v gcc). Viz https://godbolt.org/z/YvsY78f8n Všimněte si, jak to přetečení komplet vyoptimalizoval pryč.
Problém s ftrapv a fwrapv je, že je to globální nastavení
Ale aplikuje se jen na signed typy, takze si muzu udelat:
#define plus(a, b) ({int _a = (a); int _b = (b); _a + _b;})
s tim ze pomoci _Generic to muzu napsat tak, aby to jedno makro umelo i long a long long.
V C++ si muzu udelat custom typ a pretizit operatory.
Chytrý Copilot. Dalo by se to použít. Nechce se mi teď zkoušet jak by vypadal výsledný assembler na různé CPU. Na první pohled vidím: programátor asi potřebuje použít součet ale pokud přetekl tak skok pryč. Tedy:
1) Sčítání, test, podmíněný skok. Tři instrukce, dvě proměnné.
2) Odečítání, test, podmíněný skok, sčítání. Čtyři instrukce, dvě proměnné a konstanta k tomu.
Naštěstí máme optimalizující překladače a očekával bych pro oba zdrojáky stejný výstup, na x86 tedy dvě instrukce (součet a skok rovnou podle příznaku přetečení). Nicméně, co s tím udělá zapnutí kontroly přetečení, jak to zpracují různé překladače pro různá CPU, netroufám si odhadnout všechny kombinace.
Můj názor je, že platný rozsah proměnných má být v programu hlídán explicitně, že má být jasnou součástí zdrojáku a ne paušálně někde v překladači. Překladač má jen přeložit co zdroják říká, případně řvát varování, ale nemělo by se spoléhat že by k funkčnosti kódu něco automaticky a všude přidával.
Na x86 je to tak, stačí dvě instrukce, ale na jiných CPU nevím jak to mají. Teda jestli to mají všechny…
Občas mi v C/C++ chybí možnost použít tuto schopnost procesorů, ale platformově nezávislým, čistým C++ . Neumím a přitom bych občas trochu využil možnost říct překladači že mě zajímá jestli došlo k přetečení/podtečení, nebo naopak že mi nemá psát warning o možném přetečení ke konkrétnímu výrazu, ale ve zbytku zdrojáku jo.
X86 a AArch64 mají flagy, takže tam to jsou 2 instrukce. RISC-V flagy nemá, takže tam je potřeba jiný přístup.
Jinak co se týče přetečení tak compilery na to mají vlastní built-ins (intrinsics), které se dají použít - třeba __builtin_sadd_overflow, atd...
13. 5. 2024, 21:19 editováno autorem komentáře
Ono overit preklad z mnohych jazykov je mozne jednoducho a online... Obe verzie som ksusil a moc sa nepodobaju...
https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,selection:(endColumn:2,endLineNumber:16,positionColumn:2,positionLineNumber:16,selectionStartColumn:2,selectionStartLineNumber:16,startColumn:2,startLineNumber:16),source:'//+Type+your+code+here,+or+load+an+example.%0Aint+owerflow_check(int+a,+int+b)+%7B%0A++++if(a%2Bb+%3C+a)+%7B%0A++++++++return+0%3B%0A++++%7D+else+%7B%0A++++++++return+1%3B%0A++++%7D%0A%7D%0A%0Aint+owerflow_check_copilot(int+a,+int+b)+%7B%0A++++if(__INT_MAX__+-+a+%3C+b)+%7B%0A++++++++return+0%3B%0A++++%7D+else+%7B%0A++++++++return+1%3B%0A++++%7D%0A%7D'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:50,l:'4',n:'0',o:,s:0,t:'0'),(g:!((h:compiler,i:(compiler:g141,filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'0',trim:'1',verboseDemangling:'0'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:c%2B%2B,libs:!(),options:,overrides:!(),selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'+x86-64+gcc+14.1+(Editor+%231)',t:'0')),k:50,l:'4',n:'0',o:,s:0,t:'0')),l:'2',n:'0',o:,t:'0')),version:4
Pripadne skrateny link: https://godbolt.org/z/crT6cP1eP
PS: Share menu je vpravo hore...
Já mám naopak dojem, že jiné jazyky hlídají a dělají spoustu věcí samy, ale právě C/C++ se úporně drží toho aby zkompilovaný program dělal přesně jen to co zdroják výslovně říká. I kdyby to bylo sebevíc špatně, tak to přeloží doslova. A to se mi dost líbí.
Ani warningy nepíše když to někdo nechce, ani optimalizace nemusí dělat. Vlastně nedělá nic, jen to dělá dost důkladně. Tak mi to aspoň připadá.
I C se dá kompilovat mnoha různými způsoby. Přece jen vznikl v dost punkových dobách, takže dovoluje až nečekané divočiny. Linus to vyřešil pragmaticky tak, že linux není psaný v C ale v GCC dialektu a vyžaduje některá dost specifická nastavení. I C++ by mohl pořešit stejným způsobem.
Jeho hlavní problém s c++ je myslím v tom, že ten jazyk přitahuje specifický typ programátorů, jejichž přístup Linus opravdu nemusí. Aspoň to vidím jako jádro jeho slavné kritiky c++.
Co je na kontrole přetečení chytrého?
Jinak kompilátor je rozhodně důslednější a pozornější než člověk. My lidi prostě nemáme mentální kapacitu na to přemýšlet jestli každé počítání s intama náhodou nemůže přetéct. Respektive máme, ale když ji utopíme v téhle tupé mechanické dělničině, tak nám jí zbývá krutě málo na ty chytré věci.
Jinak napsat v Cčku kontrolu, jestli součet dvou (signed) intů náhodou nepřeteče není vůbec jednoduché. Občas si na tom vylámou zuby i těžké váhy. Linux se tomuhle peklu pragmaticky vyhnul a vyžaduje -fwrapv. A každý překladač má vlastní nestandardní sadu intrinsik pro tuhle základní věc.
Když jazyk po programátorovi něco chce, tak by mu měl aspoň poskytnout prostředky, jak to kontrolovat.