C-Entwickler kämpfen gegen undefiniertes Verhalten
Reducing undefined behavior in the C language
Martin Uecker erklärte auf den Kernel Recipes 2026, warum C trotz seiner Fallstricke weiterhin eine großartige Sprache ist. Undefiniertes Verhalten erlaubt Compilern aggressive Optimierungen, führt aber zu berüchtigten Fehlern wie „nasal demons“. Der C2y-Entwurf entfernt 45 der rund 100 undefinierten Verhaltensweisen. Werkzeuge wie Sanitizer und statische Analysatoren verbessern die Lage, doch vollständige Speichersicherheit bleibt ein fernes Ziel.
Das Problem, so Uecker, ist, dass der Standard einem Compiler erlaubt, als Reaktion auf undefiniertes Verhalten alles zu tun, bis hin zur Beschwörung von Nasendämonen.
- pizlonator
Es ist cool, dass Fil-C hier erwähnt wird, aber es wird auch unter Wert verkauft. TFA verkauft auch CHERI unter Wert. Fil-C findet nicht nur „viele Temporal-Safety“-Bugs. Es schließt Memory-Safety-Bugs (sowohl spezielle als auch temporale) für Exploit-Autoren aus und schreibt der gesamten Sprache eine strikte Semantik zu. CHERI trifft einige andere Kompromisse, liefert aber ebenfalls eine ausreichend strikte Semantik, sodass Memory-Safety-Exploits nicht funktionieren werden. Sowohl CHERI als auch Fil-C sind umfassender als Rust, da sie das Problem auf ABI-Ebene angehen (und man somit nicht das Problem bekommt, dass der Schutz nur für die Teile gilt, die in der sicheren Teilmenge einer neuen Sprache neu geschrieben wurden). Man könnte behaupten, Rust sei besser, was die Compile-Zeit angeht, aber das macht keinen signifikanten Unterschied, wenn man sich um die Definierbarkeit der Semantik oder die Ausnutzbarkeit sorgt.
- chasil
„Einige Honeywell-Maschinen zum Beispiel hatten Neun-Bit-Bytes.“
OS 2200 hat 36-Bit-Wörter. Es ist immer noch eine unterstützte Plattform.
https://en.wikipedia.org/wiki/UNIVAC_1100/2200_series
Diese Plattform war die erste SMP-UNIX-Implementierung:
„Jede von Sperry gelieferte Konfiguration, einschließlich Multiprozessor-Konfigurationen, kann das UNIX-System ausführen.“
https://www.nokia.com/bell-labs/about/dennis-m-ritchie/other...
- vrighter
Ich habe tatsächlich darüber nachgedacht, ein Spaßprojekt zu starten, das UB tatsächlich zu UB machen würde. Nicht die Art von „in der Praxis wird vorzeichenbehafteter Überlauf von den meisten modernen CPUs gleich behandelt“. Ich meine einen RNG und ein Compiler-Plugin-System, um neue Bs passend zu den Us einzuführen.
- hn_submit
Ich glaube, das ist der falsche Ansatz, da C meiner Meinung nach einfach „High-Level-Assembler“ für Systemprogrammierung ist. Sobald man Laufzeitverhalten hinzufügt, um Undefined Behavior (UB) zu bekämpfen, explodieren die Ausführungszeiten. Und statische Analyse kann nur so weit gehen, ohne die Compile-Zeiten explodieren zu lassen.
C ist „das richtige Werkzeug für die richtige Aufgabe“, nämlich Betriebssysteme und deren Code, der tausendfach pro Sekunde aufgerufen wird. Man kann sich in diesem Code nicht einmal ein Jota an Laufzeitprüfungen leisten. Der Entwickler muss wissen, was er tut, oder er sollte aus der Küche verschwinden.
Wir sollten die Verwendung von C in der Anwendungsprogrammierung entmutigen und Anwendungsentwickler in Richtung speichersicherer Sprachen wie Rust oder Go drängen.
Und ich bin mir nicht einmal sicher, ob Rust diesen Fall in Bezug auf UB löst.
- rwmj
Ich denke, https://en.wikipedia.org/wiki/CompCert sollte zumindest erwähnt werden, auch wenn es keine freie Software ist („source available“-Lizenz). Es ist der tatsächliche Weg, wie Unternehmen mit großen C-Codebasen in sicherheitskritischen Branchen ihren Code verifizieren.
- vbezhenar
Mein Hauptproblem mit UB in C ist, dass es still ist.
Ich bin damit einverstanden, dass der Compiler wilde Dinge tut, okay, meinetwegen. Nun, ich bin nicht einverstanden, aber ich kann es in dieser verrückten Welt akzeptieren.
Aber ich will laute Warnungen! Wie WARNUNG: Dieser Bedingungsoperator wurde auf einen Zweig reduziert, weil eine frühere Division durch Null UB ist. Und jetzt kann ich es bemerken und umschreiben oder einfach die Bedingung entfernen.
Ich verstehe, dass dieser Code das Ergebnis einer Makro-Expansion sein kann. Das ist okay. Makros sollten entweder einige Pragmas enthalten, um bestimmte Diagnosen vorübergehend zu deaktivieren, oder der Benutzer sollte die Makro-Verwendung mit diesen Pragmas umgeben, wenn er das Makro nicht bearbeiten kann. Das passiert bereits mit anderen Warnungen.
Oder vielleicht könnte der Compiler intelligent genug sein, um Makro-Expansion von einem ehrlichen Benutzerfehler zu unterscheiden, ich weiß es nicht.
Ich erinnere mich, als der C++-Compiler einfach das Funktions-Epilog entfernte, wo ich eine einfache Endlosschleife geschrieben hatte. Das war so verrückt. Also statt in die Endlosschleife einzutreten, führte mein Programm einfach die Funktion weiter aus, die zufällig darunter gelinkt war. Stell dir vor, das zu debuggen. Null Diagnosen.
- 1vuio0pswjnm7
1790620504 | Reducing undefined behavior in the C language | https://lwn.net/SubscriberLink/1095811/b9325731ea9b61e0/ | https://news.ycombinator.com/item?id=49882419 | 15 comments
1790673092 | Reducing undefined behavior in the C language | https://lwn.net/SubscriberLink/1095811/efcdbcf080cfa4c6/ | https://news.ycombinator.com/item?id=49890290 | 0 comments
- nine_k
Ich finde die Situation um UB ehrlich gesagt verblüffend. Meines Wissens entstand die Idee in Zeiten sehr anämischer Compiler, die Dinge übersetzten, die keinen eindeutigen Sinn ergaben, oder auf bestimmten Architekturen unvorhersehbar funktionierten. Aber warum ist das jetzt, 50 Jahre später, immer noch so?
Wenn ein Compiler UB erkennen kann, ist meiner Meinung nach die einzig vernünftige Vorgehensweise, das Programm sofort zu beenden, statt „nasal demons“ freizusetzen. (Und natürlich werden viele Bugs durch -Wall zerschlagen, was die Standardeinstellung sein sollte.)
Natürlich kann nicht jedes UB zur Compile-Zeit erkannt werden, wie das Lesen der Padding-Bytes in einem im Artikel erwähnten Struct. Ich wünschte, es gäbe eine Möglichkeit, sich von jeglichem UB abzumelden, auf eine willkürliche, aber vorhersehbare Weise (ein sofortiger Absturz wäre in Ordnung), so etwas wie -fsanitize, aber für jegliches UB, das zur Laufzeit auftreten könnte. In so manchem wichtigen Code würde ich den Performance-Verlust tolerieren, das mag billiger sein, als sich mit den Nachwirkungen oder einer ausgenutzten RCE herumzuschlagen. Ich sehe allerdings ein, dass das nicht ganz realistisch ist.