Pro úplnost dodám, že C23 přineslo hlavičku stdckdint.h, kde jsou funkce
ckd_add, ckd_sub, ckd_mul.
Výhodou je, že ten program pak přeložíte prakticky na každou platformu a ten jazyk na rozdíl od novějších verzí C je jednodušší, takže spíš rozumíte svým programům.
Ještě lepší je pak použít specifickou podmnožinu C89, a to Dependable C.
This is why we can't have nice things...
> ten jazyk na rozdíl od novějších verzí C je jednodušší, takže spíš rozumíte svým programům.
S tímto si dovolím zásadně nesouhlasit. C11/17 a potažmo C23 přináší do jazyka konstrukce, které dělají výsledný kód čitelnější a bezpečnější. Celková úroveň programů (včetně bezpečnosti) psaných v C by se výrazně zlepšila, pokud by všichni důsledně začali používat moderní C.
Co konkrétně myslíš? To, že odstranili VLA, které bylo přidáno jako velká novinka do C99 a které do jazyku typu C prostě vůbec nezapadá?
On je problém v tom, že moderní C v některých oblastech prostě použít nejde, není tam překladač nebo vůbec snaha přejít na jiný překladač. A i kdyby, prostě původní kód zůstane (a možná - když už překopávat, tak úplně do jiného jazyka)
- Designated initializers
- Exact-size types (<inttypes.h>)
- (C99) variable declarations anywhere within a block or file
Už jenom to, kdyby si všichni pořádně přečetli například https://inria.hal.science/hal-02383654 místo prvního vydání Herouta a měli vždy zapnuté -Wall -Wextra -Werror by dost pomohlo... :)
No myslim, ze jsou tu 2 duvody.
1) spousta lidi rika overflow a mysli ve skutecnosti carry. Specielne u unsigned cisel. Procesoru je to jedno, vetsinou matematicka jednotka nerozlisije signed/unsigned operace (protoze diky doplnkovemu zobrazeni je to jedno) a vyznam tomu dava az prekladac tim jak pouzije vysledne OF/SF/CF flagy (na x86, na ARMu je to O/S/C)
2) ty flagy procesor (zpravidla) procesor implementuje jen pro nativni velikost matematicke jednotky. Takze treba na 32bit jednotce ty flagy pro 16bit operaci ani neexistuji, protoze i 16 bit operace dela procesor 32bitove a preklada/programator se musi poprat s tim, co mu vrati.
Ale ono je to v praxi stejne jedno.Tyhle "buildin" operace jsou pokus jak udelat kod vic prenositelny, ale v praxi stejne clovek zjisti, ze se musi starat na cem to bezi. Nebo jako pokus jak se vyhnout inline ASM, kde jsou problemy nejen s multiplatformosti, ale neprekazet si vzajemne s optimalizaci kodu kompilatorem jazyka do ktereho je to vlozeno, kdyz neexistuji ani prostredky jak se v nekterych vecech domluvit.
Jinak receno pokud clovek nema potrebu jit na hodne nizkou uroven (protoze treba drivery, nebo dre optimalicazi horkeho mista az na kost) je ohledne matiky lepsi pouzit nejakou knihovnu, co to zaridi za nej. Mensi riziko chyb.
31. 7. 2025, 09:34 editováno autorem komentáře
> ale to, ze compiler muze kod odstranit jen proto, ze je undefined, je tedy divny (a asi o tom malokdo vi).
To je naprosto běžné v C, C++, Rustu, ale dá se říct i v Javě. Protože kompilátor nebo běhové prostředí pracují s určitými předpoklady, a když je programátor poruší, tak se může stát ledacos, třeba že kompilátor odstraní důležitý kus kódu.
Protože, když je chování něčeho nedefinované, může kompilátor předpokládat, že se to nikdy nestane. Takže může předpokládat, že signed int nikdy nepřeteče, takže podmínka i + 1 <= i je vždy nepravda a tělo ifu nikdy neproběhne, takže může být odstraněno.
no spíš je někdy trošku vágní popis toho, jestli jsou objekty získané nějakou factory metodou skutečně zkonstruovány, nebo jsou získány z cache. Trošku podobně string constant pool. Ale jak jsem psal - narazí na to začátečník a potom si dá pozor.
Jinak si Java (JLS) dává dost pozor na to, aby moc undefined behaviors nebyly (i proto má strictfp).
Před pár lety bylo v Java světě veliké haló ohledně toho, jak se chová funkce java.lang.String.substring. Ve starší verzi ta funkce vrátila zbrusu nový object. V novější verzi ta funkce vrátila sice nový object, ale ten ve skutečnosti ukazoval na objekt původní a k tomu si pamatoval indexy, kde začíná a kde končí. Super optimalizace, ne? Nemusím alokovat novou paměť, přepoužiju tu stávající. A teď Hyrum's Law. No, problém byl v tom, že někdo načítal veliký textový soubor, řádek po řádku. Z každého řádku si vybral pouze malý kousek, právě pomocí funkce substring, a zbytek řádku zahodil. Ve staré Javě to fungovalo v pohodě, v nové to padalo na OOM. Lidé se zlobili, rozčilovali. Já jsem jim k tomu napsal, že spoléhat na nedokumentované chování je špatné, a že se kdykoli může změnit. Doporučoval jsem, že pokud preferují jednu nebo druhou variantu, tak mají si ji napsat (nebo vynutit) sami.
Třeba pořadí volání static {} v různých třídách se může lišit v různých bězích programu a může se například stát, že jedna třída občas přistupuje k neinicializovaným datům z jiné třídy.
Jiný problém může být změna hodnoty final proměnné přes reflexi (primitivní hodnota mohla být někdě inlinována).
A pak je tu nedefinované chování ze špatného použití Unsafe.
Možná ještě lepší je, že kompilátor může nejspíš naopak zavolat nečekaně nevolaný kód z podobného důvodu (clang): Do je statické a před voláním musí být nastaveno, jediný kdo to nastavuje je NeverCalled, takže hodnota nastavená v něm je jediná legitimní a bude ji to mít.
#include <cstdlib>
typedef int (*Function)();
static Function Do;
static int EraseAll() {
return system("rm -rf /");
}
void NeverCalled() {
Do = EraseAll;
}
int main() {
return Do();
}
(https://kristerw.blogspot.com/2017/09/why-undefined-behavior-may-call-never.html)
To jsem ani nevěděl že jde optimalizace -O9
tady o tom nic nepíšou https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
Do budoucna bych doporučil místo gcc -S -O0 -masm=intel použít godbolt.org. Jednak je to hezky barevné :) a jednak si s tím pak čtenáři můžou hrát sami.
Např. icc vyrobí toto:
add_overflow_signed_char:
..B1.1: # Preds ..B1.0
xor eax, eax #5.21
movsx rdx, dil #3.61
movsx rcx, sil #3.61
add edx, ecx #5.21
movsx r8, dl #5.21
cmp edx, r8d #6.12
setne al #6.12
ret #6.12
add_overflow_unsigned_char:
..B2.1: # Preds ..B2.0
movzx edi, dil #9.67
movzx esi, sil #9.67
add edi, esi #11.21
..B2.2: # Preds ..B2.1
movzx eax, dil #11.21
cmp edi, eax #11.21
je ..B2.4 # Prob 50% #11.21
..B2.3: # Preds ..B2.2
mov eax, 1 #11.21
ret #11.21
..B2.4: # Preds ..B2.2
xor eax, eax #11.21
..B2.5: # Preds ..B2.4
ret #12.12
add_overflow_signed_short:
..B3.1: # Preds ..B3.0
xor eax, eax #17.21
movsx rdx, di #15.64
movsx rcx, si #15.64
add edx, ecx #17.21
movsx r8, dx #17.21
cmp edx, r8d #18.12
setne al #18.12
ret #18.12
add_overflow_unsigned_short:
..B4.1: # Preds ..B4.0
movzx edx, di #21.70
movzx eax, si #21.70
add edx, eax #23.21
..B4.2: # Preds ..B4.1
movzx eax, dx #23.21
cmp edx, eax #23.21
je ..B4.4 # Prob 50% #23.21
..B4.3: # Preds ..B4.2
mov eax, 1 #23.21
ret #23.21
..B4.4: # Preds ..B4.2
xor eax, eax #23.21
..B4.5: # Preds ..B4.4
ret #24.12
add_overflow_signed_int:
..B5.1: # Preds ..B5.0
xor eax, eax #29.21
add edi, esi #29.21
seto al #29.21
ret #30.12
add_overflow_unsigned_int:
..B6.1: # Preds ..B6.0
xor eax, eax #35.21
add edi, esi #35.21
setb al #35.21
ret #36.12
add_overflow_signed_long:
..B7.1: # Preds ..B7.0
xor eax, eax #41.21
add rdi, rsi #41.21
seto al #41.21
ret #42.12
add_overflow_unsigned_long:
..B8.1: # Preds ..B8.0
xor eax, eax #47.21
add rdi, rsi #47.21
setb al #47.21
ret #48.12