Nieco take uz davno jestvuje, vola sa to Checked C - file:///C:/Users/jozef.gajdos/Downloads/checkedc-v0.7.pdf
to je navod, ze kdyz si udelas u sebe uzivatele josef gajdos a stahnes si pdf o checked-c, tak pak ho najdes v uvedene ceste.
jj těch projektů je víc. Checked C má asi (mnohem?) lepší teoretický základ, resp. ten popis je úplně klasicky akademický :-) C3 je založenej víc prakticky a ty kontroly ukazatelů a polí jsou tam jen jedna část vylepšení.
Podle mě ze stejného důvodu, proč je to v Go - při parsingu (jak počítačem, tak při čtení zdrojáku člověkem) není zpočátku jasné, jaký objekt se deklaruje; musí se vlastně dojít až na konec celé deklarace a tam se rozhodne (jestli je to funkce, konstanta nebo proměnná). Takže tam přidali to klíčové slovo. A navíc to potom umožňuje i čitelný zkrácený zápis funkce s tělem za =>
Není zbytečné. Najdi si "most vexing parse". Deklarace funkcí v C je dost nejednoznačná a hlavně v C++ to má dost drsné důsledky.
C-like syntaxe se používá protože je známá, ne proto že by byla nějak zvlášť dobrá.
Jinak osobní názor (který se do článku nehodí): jako je to pěkné, některé myšlenky v C3 jsou zajímavé, ale C to myslím nenahradí. To ale zatím ani žádný jiný jazyk zmíněný v článku. Největším plusem je naprosto skvělá podpora C ABI, ale i autor C3 je obecně skeptický: https://c3.handmade.network/blog/p/8486-the_case_against_a_c_alternative
Nicméně pro psaní aplikací - na druhou stranu proč to nezkusit.
Nedávno jsem zrovna přepsal několik programů z C3 do Rustu jako takový experiment. Jeden mám dokonce veřejně (C3, Rust).
Ve výsledku mi přijde, že C3 má lepší error handling, lepší makra
a snáze se volají systémové funkce.
V Rustu jsem si o něco jistější, že nepřistupuji k nějaké paměti, co nemám, ale občas to bylo za cenu ošklivějšího kódu (například nemohu do nějaké funkce předat mut referenci na strukturu, pokud na field dané struktury mám referenci jinde, i když by mutable přístup ke struktuře neměnil field, co už jinde referencuji - tohle typový systém Rustu nezvládne zachytit).
Hlavní nevýhoda Rustu mi přijde složitost psaní vlastních datových struktur - dokážu ji napsat v unsafe kódu, ale už bohužel nedokážu vytvořit bezpečné rozhraní. Ta složitost Rustu je vidět například na node.rs ze standardní knihovny. Když jsem to přepsal do C3, tak se velikost zmenšila skoro na třetinu.
Ano, máme ho i v produkci, ale starší verzi 0.7.2. Máme v tom nějakou vizualizaci dat přes Dear ImGui a Sokol.
Chápu supbjektivní představu ošklivosti.
Chápu, že v Rustu se některé věci dělají složitě.
Když píšete, že jste "to přepsal do C3, tak se velikost zmenšila skoro na třetinu", bylo nebo nebylo to za cenu toho, že kompilátor ztratil přehled o zárukách?
Stejně jako je pro někoho důležité, aby kód byl stručný a krátký, tak pro mě je zase důležité, aby stroj poskytoval nějaké záruky. Podle mých informací je C3 unsafe by default, na rozdíl od Rustu, který je naopak safe by default. Chápu to správně?
> Když píšete, že jste "to přepsal do C3, tak se velikost zmenšila skoro na třetinu", bylo nebo nebylo to za cenu toho, že kompilátor ztratil přehled o zárukách?
Zrovna v tomto konkrétním souboru se asi o žádnou záruku nepřišlo anebo jich bylo naprosté minimum (jediné, co mě teď napadá je, že místo ukazatele NonNull používám v NodeRef.node normální ukazatel). I původní kód v Rustu používá hodně unsafe a věci jako MaybeUninit nebo ukazatele. Naopak v C3 nehrozí tolik chyb s aliasingem jako v Rustu, kde si celá implementace musí dát veliký pozor, aby např. nevrátila třeba 2 mut reference na tutéž věc.
Součástí implementace v Rustu jsou pak další soubory, které se snaží vybudovat safe rozhraní. Ty jsem také zahodil, ale ty do porovnání velikosti nepočítám. Moje implementace má totiž o dost flexibilnější rozhraní a dovoluje dělat věci, co safe implementace v Rustu neumí (např. moje porovnávací funkce nemusí porovnávat klíče stejného typu K).
Další nástupce C? Jasně, proč se už konečně nenaučit C++ (dodám pokud možno >=20).
Je taky kompatibilní z C, má jmenné prostory a od verze 20 i moduly.
s tím, že C++ je kompatibilní s C jsme se spálili velmi rychle. No aspoň že to bylo na úrovni překladače, který nás na 20 stránkách seřval a nebyla z toho skrytá chyba ve výsledném ovladači.
Ale asi jo, pokud by byla volba C3 nebo C++, tak to vyhraje C++. Bohudík máme víc možností (svět MCU).
Pro mě je C++ moc složité, chybí tam důležité funkce a naopak tam jsou zbytečnosti.
Například preferuji používat vlastní alokátory před RAII, preferuji mechanizmy chyb ze Zigu nebo C3, případně Result z Rustu, před výjimkami (abych věděl, co může skončit chybou, případně jakou). A preferuji compile-time reflexi (jako má třeba Zig nebo C3) před šablonami v C++ (něco takového chtějí přidat i do C++, ale v jiných jazycích už to je).
Naopak nepotřebuji dědičnost, RAII, virutální funkce a šablony mi stačí jen místo generik.
Tak v C++ zase naštěstí nikdo nikoho nenutí používat exceptions - je dokonce tak daleko, že se to celé dá zakázat i s RTTI, takže z toho pak je takové super lepší C :)
No a vlastní alokátory - každý lepší projekt v C++ je používá.
11. 9. 2025, 12:08 editováno autorem komentáře
Tak já RAII používám dost masivně, ani si nedokážu představit, že bych to psal jinak. Třeba že když opustím objekt transakce, tak se mi sama commitne, nebo když opustím zámek, tak se mi zámek odemkne... atd...
Co se Result vs výjimky - nevím no, vždycky si člověk řekne, tady výjimky nepoužiju a pak skončí tím, že všechno vrací chybu a všechno musím ošetřovat. Rustovsky "když error tak return error" (aka ?), je už skoro jak výjimkový systém, akorát se o tom nesmí mluvit.
U těch výjimek/chyb mi přijde důležité, zda vím, jaký kód je vyhazuje a jaký ne. Případně i jaké výjimky. Občas je dokonce fajn vědět, zda může nastat chyba, protože došla paměť.
> Třeba že když opustím objekt transakce, tak se mi sama commitne, nebo když opustím zámek, tak se mi zámek odemkne
Občas se to hodí. Ale třeba u transakcí si radši sám určím, zda se má rollbacknout nebo commitnout, a nevadí mi to mít explicitně v kódu.
U těch transakcí jsem to měl tak, že jsem musel explicitně označit transakci za úspěšnou, aby se v destruktoru commitla, jinak byl rollback. Ještě "línější" řešení je, když destruktor transakce se dívá, jestli neletí výjimka. Pokud letí, udělá rollback, jinak commit. Ale s tímhle řešením si nejsem jist. Vždycky ale ten objekt transakce má možnost manuální řízení (jako unique_lock)
Jak říkám u ošetření chyb - ze všeho může vypadnout chyba.
A pak tady máš chyby které spíš nenastanou. Například mám program, co komunikuje s externí službou přes JSON. Šance, že by ze služby vypadl nevalidní JSON je prakticky nulová. Takže vůbec tuhle situaci neošetřuju, netestuju. No ale přesto se tam hodí mít try - catch na nějaké základní úrovni, která pořeší i výjimku s nevalidním JSONem.
Na RAII je v C rozsireni uz od krale klacka - __attribute__((cleanup(destruktor))). Takze pokud po nem nekdo touzi, muze si nadefinovat makro, ktere nastavi atribut a zavola konstruktor a pak to pouzivat stylem raii_file(f, "/cesta/k/souboru", "r"); nebo raii_lock(zamek) atd.
Někdy se tohle opravdu hodí, pokud se ve funkci ošetřuje větší množství různorodých chybových cest. Někdy jde slušně tohle dokončování vyřešit i jinak, přes goto nebo třeba wrap funkce do jiné funkce, ale někdy opravdu potřebuji cleanup, pokud nechci výrazně hnusnější kód.
C++ rozhodně s C kompatibilní není. Co hůře některé stejně vypadající konstrukty a klíčová slova majíí v C a C++ zcela jiný význam (např. inline ... v C znamená že se má funkce inlinovat, v cpp to znamená že ji definujete jako slabý symbol pro linkování a má být deduplikována, nebo třeba typická zrada, kdy "string" je v C char* ale v cpp const char*).
No, zcela jiný význam...
Inline v C taky "jen" umožňuje inlinování tím, že řeší konflikty symbolů při linkování. Jen to dělá malinko jinak. Takže v C je třeba udělat trochu víc práce, aby se úspěšně slinkoval i debug build. Tam i tam to pak překladače můžou interpretovat i jako hint, že se to zainlinovat má.
V C jsou sice string literály char *, ale zápis je nedefinované chování. Takže to člověk do toho const char * chce přiřadit už jenom proto, aby si náhodou nenaběhl na vidle.
Ahoj Pavle, díky za další skvělý článek, jako vždy.
Jen mám pocit, že ten kód pro generování fraktálu v C sis asi nepřekládal... Máš tam všude špatně reference na položky struktury:
resolution>-width
ahoj, díky.
Ale jo překládal, ale originál v repu https://github.com/tisnik/c3-examples/blob/master/introduction/renderer.c ne to, co je vloženo do článku.
Při transformaci do HTML (ty jeho > atd.) se asi něco...nepovedlo... Díky za upozornění, hned opravím.
Update: tak opraveno, ještě jednou dík.
11. 9. 2025, 10:10 editováno autorem komentáře
ObjC je stále systémový jazyk macOS a iOS, Swift je až nad tím. Podobně jako má Microsoft C#, ale WinAPI je v C/C++.
tu faktorialy co jedou od jednicky nahoru k n by asi nemusely zbytecne v prvnim kole nasobit jednickou?
a ten druhy factorial_recursive(int n) co jede od n dolu bych radeji dal n jako unsigned, neni tam test na zaporne cisla tak pri spatnem vstupu pojede dolu hezky dlouho
Ona je to jenom ukázka kódu. Nikdo příčetný by nepočítal faktoriál rekurzivně, doufám. Tak mi nepřijde užitečné tu funkci optimalizovat.
je to tak
ale k te rekurzi jsem se chtel dostat, protoze nektere prekladace (a na nektere platformy!) to krasne prevedou na smycku:
factorial_recursive:
mov eax, 1
test edi, edi
je .L4
.L3:
imul eax, edi
sub edi, 1
jne .L3
ret
.L4:
ret
(tedy az na ten zbytecnej ret na konci)
bylo by zajímavé zjistit, jak by se třeba tvářili ve škole, kdyby někdo odevzdal výpočet faktoriálu jako tabulku šestnácti hodnot (já bych to obhajoval takto: žádný kód==bezchybný kód :-)