Ono těch chyb v GNU Coreutils je ovšem taky hodně, jen se o každé nepíšou zprávičky. Řada z nich se týká právě paměťové bezpečnosti, takže má smysl se tím zabývat. Přechod na úplně novou implementaci ovšem bolí.
> Řada z nich se týká právě paměťové bezpečnosti, takže má smysl se tím zabývat.
Je otázka, jestli Rust je to správné řešení.
Kdyby tohle chtěl Rust vyřešit, tak by IMO neřekl, že aliasing pointerů nebo referencí je nedefinované chování. Jenže oni to řekli, protože chtějí optimalizace. Takže na chyby náchylnou práci nechali tvůrci Rustu na programátorech.
Jenže konkrétně aliasing je hodně velký průser. V C++ je aliasing dokonce by-design v celé standardní knihovně (např. iterátory, že jo...). Takže compiler vždycky musí předpokládat aliasing, což omezuje optimalizace, které může udělat.
Za mě omezit nebo úplně odstranit aliasing je mnohem lepší než cesta C/C++, kde je restrict, který ale skoro nikdo nepoužívá.
No hlavně by programátor C++ měl dodržovat strict aliasing rules. Pak mají překladače volnější ruce.
Přistupovat do jedné proměnné přes dva ukazatele (různého typu) je UB.
Používání dvou iterátorů na dvěma různými ranges se předpokládá, že se nepřekrývají, jinak UB
Přístup iterátorem mimo range, ve kterém je platný je UB.
Jinak jsou překladače v optimalizacích velice dobré, umí detekovat, kdy hrozí nebezpečí aliasingu a kdy ne (a v jaký okamžik je třeba zneplatnit cachovaný obsah všech ukazatelů, kvůli nebezpečí aliasingu)
Spíš mi vadí, že pokud překladač něco takového vidí, že nevydá žádné warningy, nebo že jsou ty warningy na nějakém hluboko-obskurdním levelu defaultně vypnuté.
Ono je rozdíl mezi "dodržovat něco" a když je něco "vyžadované".
Já sice hodně dělám C++, ale vidím hodně výhod právě v rustu - ty omezení mají smysl a třeba iterátory v rustu jsou mnohem líp vymyšlené než ty v C++. Ale nechci se tady pouštět do nějaké hlubší diskuze. Dlouhodobě se asi přikláním spíš k rustu než C++, ale C++ a golang mě teď živí.
Ano výhodou Rustu je, že to kontroluje a když není něco splněno, tak se to nepřeloží
- nevýhodou je pak boj s překladačem
Oproti tomu v C/C++ je spoustu věcí jednoduše označeno UB, a pokud to člověk použije, je to jeho chyba.
A ano, programátoři mají často problém se skillem, proto je Rust oblíbený, který jim spoustu věcí nedovolí a vodí je za ručičku.
Nedávno jsem řešil jak v rustu udělat permutaci prvků v poli safe. Visí to na swapování, které samo osobě není safe code. Pokud chci permutovat do jiného kontejneru, tak je to unsafe taky (nebo vyžadovat použití Option). Prostě boj s překladačem (bylo to cvičení na prozkoumání výhod a nevýhod destruktivního move).
Jenže to je právě ten problém.
Já dělal na projektech v C++ co měly miliony řádků kódu a to nejde uhlídat v teamu 10-20 lidí s různou senioritou. UB je věčný boj - znamená to nasadit UBSAN, ASAN, MSAN a TSAN (který má nějaké celkem vážné omezení) a ani sanitizéry nenajdou všechno a nejdou použít na některé testy, které třeba vyžadujou hodně paměti (ASAN) nebo výkonu (UBSAN).
Takže já bych řekl, že právě ty omezení v rustu co přináší memory safety začínají být účinné až se ten projekt rozroste. Třeba refactoring většího množství kódu v C++ je noční můra a pokaždné, když se udělá, tak se něco rozbije.
No já taky. Ale většinou je to skill issue.
- drtivá většina dneska neprogramuje v C++ ale v C with classes
- dodneska se masivně zneužívá preprocesor, někdy i blbě (například #define SUM(a,b) a+b )
- a nebo se programuje ve stylu Javy kdy mám rozvinutou hierarchii tříd, které nedávají smysl.
- minimum lidí zná chytré ukazatele
- velké množství lidí používá "raw" pole.
Je to problém. Rust těží z toho, že nic takového tam nejde dělat. V C++ to taky lze psát bezpečně. Chce to nasadit nástroje, které odmítnou jakýkoliv kód, který nese známky C stylu programování. Takové existují.
Odmítnout kód, který nese známky C, nestačí, pořád nelze psát v C++ bezpečně. Problémem C++ jsou lifetimes, protože C++ překladač nemá dostatek informací k tomu, aby dokázal inferovat lifetime objektu a tím pádem ani nemůže vědět, jestli je ukazatel na objekt stále platný, nebo ne.
Triviální přiklad:
auto a = std::make_unique<int>(0); auto b = std::move(a); std::cout << *a; // UNDEFINED BEHAVIOR
Všechno je to moderní C++ se smart pointery, žádné C a stejně je tam UB. Takové věci bohužel nejde na větším projektu ve více lidech uhlídat a C++ tooly to z principu taky nemůžou umět.
No to bylo jako "na začátek"
Zrovna tenhle příklad je snadno detekovatelný.
Bohužel je, všechny tři překladače to nedokáží detekovat, což považuju za chybu překladačů. Je to dokonce tak legrační že
gcc kód přeloží jako
mov eax, DWORD PTR ds:0
Což mě hlava tedy nebere, že to někomu nepříjde divný. To je snad na bug report na gnu!
clang pro jistotu celý příkaz vynechá (nezavolá cout)
Microsoft to přeloží tupě bez dalších optimalizací (takže udělá dereferencu null)
Hodně jsem zklamán zejména ze schopností překladačů. Ty překladače to UB vidí, ale nevypíšou žádnou hlášku.
Teď vám píše :-D
Zcela vážně, netuším co je to memory unsafe. V C++ programuju denně a sigsegv, ani memory leak jsem pořádně neviděl pořádně roky. Pokud se budu hodně snažit, třeba psát nějakou super optimalizaci v c-like kódu, tak samozřejmě takovou chybu udělám. Pokud ale budu správně používat všechny hlavní vlastnosti C++, tak to fakt je skill issue tam udělat chybu. Lidi jsou často děsný prasata a dokážou vymýšlet strašný elektrárny.
Vážně je skill issue nevšimnout si, že se provedl move na smart pointeru a pak se později dělá jeho dereference? V kódu to nemusí být hned za sebou, move se udělá v jedné funkci a dereference v jiné a je z toho UB. Projekty se v čase vyvíjí a lidé do nich přicházejí a zase odcházejí a není v možnostech člověka udržet větší projekt celý v hlavě, zvláště když na něm pracuje více lidí současně.
Není to skill issue - skill issue je jen takový argument od lidí, co už dělají 2 dekády C++ a myslí si, že chyby nedělají. Jenže oni o nich jen neví a pak budou nějaký problém hledat 2 týdny, až se to projeví...
Já chyby dělám taky, a proto píšu hodně testů - protože pak můžu spát a dělat i refactoring, kterému pak víc věřím.
Jediná možnost, jak psát relativně safe C++ kód je rozdělit věci na malé otestovatelné celky a ty pak propojovat - jenže to někdy vyžaduje víc času, než který člověk v realitě má.