Der Blogbeitrag analysiert C-Sprachkonzepte aus der Perspektive eines Entwicklers, dessen Erstsprache Rust war, und beleuchtet historische Designentscheidungen sowie typische Fallstricke. Im Fokus stehen sprachliche Unterschiede wie nachträglich integrierte Booleans, nullterminierte Strings und plattformabhängige Integer-Breiten. Ziel ist es, die Eigenheiten von C im Vergleich zu modernen Speichersicherheits-Paradigmen greifbar zu machen.
Wichtigste Punkte
Booleans: C besaß in frühen Versionen keinen primitiven Datentyp für Booleans; erst C99 fügte ihn über den optionalen Header <stdbool.h> hinzu, während C23 ihn schließlich als echte Sprachprimitive etablierte.
Nullterminierte Strings: Im Gegensatz zu Rusts dynamischen Strings (die Längen-Metadaten speichern) nutzen C-Strings ein Null-Byte (\0) als Endmarkierung, was Speicheroverhead bei leeren Strings und Pufferüberläufe bei fehlender Terminator-Handhabung verursacht.
Plattformabhängige Integer-Breiten: Die Bit-Breiten von C-Typen wie long variieren je nach Zielplattform (z. B. 64-Bit unter Unix, 32-Bit unter Windows), weshalb für plattformübergreifenden Code feste Breiten aus <stdint.h> (z. B. int64_t) zwingend empfohlen werden.
Fehlerbehandlung: C unterstützt keine standardisierten Summentypen wie Rusts Result, sondern nutzt Fehlerindikatoren wie -1, NULL-Zeiger oder den Thread-lokalen Fehlerstatus errno, ohne automatische Absicherung gegen unbehandelte Fehler.
Array-Zerfall und Zeigerarithmetik: Arrays zerfallen beim Übergeben an Funktionen implizit in Rohzeiger (wobei sizeof die Zeigergröße statt der Array-Größe liefert); C bietet zudem keine Referenzen oder Borrow-Checker, sondern erlaubt direkte Zeigerarithmetik.
Diskussions-Highlights
Array-Kommutativität: Kommentatoren hoben die mathematische Einfachheit von C hervor, bei der Array-Zugriffe kommutativ sind (z. B. entspricht der Ausdruck c[1] exakt 1[c]).
Tooling und Warnungen: Mehrere Stimmen betonten, dass die Sicherheit in C stark von externen Werkzeugen wie Compiler-Warnungen (-Wall, -Wextra), Sanitizern (ASAN, UBSAN, TSAN) und statischer Analyse (Clang-Tidy) abhängt, statt allein auf die Sprachspezifikation zu vertrauen.
Speicherfehler-Behandlung: Kritisiert wurde die im Artikel vorgeschlagene Programmunterbrechung via exit() bei malloc-Fehlern; idiomatischer ist die einfache Fehlerweitergabe durch Funktionsrückkehr.
Bit-Feld-Optimierung: Als Lösungsansatz für speichereffiziente Booleans wurde die C-Syntax für Bitfelder (z. B. uint8_t some_bool : 1) genannt, um das Byte-Alignment zu umgehen.
Grenzen der Softwaresicherheit: Diskutiert wurde, ob eine rein softwarebasierte Speichersicherheit (wie in Rust) hardwarebasierte Angriffe wie Rowhammer verhindern kann, wobei künftige Entlastung durch automatisierte Analysen und Code-Generatoren erwartet wird.
Glossar
malloc: Eine Standardbibliotheksfunktion in C zur dynamischen Speicherreservierung auf dem Heap.
NULL-Zeiger (Null Pointer): Ein Zeigerwert, der auf keine gültige Speicheradresse verweist und oft zur Signalisierung von Fehlern oder leeren Zuständen genutzt wird.
Segfault (Segmentation Fault): Ein schwerwiegender Laufzeitfehler, der auftritt, wenn ein Programm versucht, auf einen nicht autorisierten Speicherbereich zuzugreifen.
Borrow Checker (Entleihungsprüfer): Eine statische Analysekomponente (z. B. in Rust), die die Einhaltung von Speicherbesitz- und Lebensdauerregeln zur Kompilierzeit erzwingt.
ASAN (AddressSanitizer): Ein compilerbasiertes Werkzeug zur schnellen Erkennung von Speicherfehlern (wie Pufferüberläufen) in C- und C++-Programmen.
UBSAN (UndefinedBehaviorSanitizer): Ein Werkzeug zur Erkennung von undefiniertem Verhalten zur Laufzeit in kompiliertem Quellcode.
Rowhammer: Eine Hardware-Schwachstelle in dynamischen RAM-Chips (DRAM), bei der durch wiederholten Zugriff auf bestimmte Speicherzellen benachbarte Bits manipuliert werden können.
FFI (Foreign Function Interface): Ein Mechanismus, der es Programmen in einer Programmiersprache ermöglicht, Routinen aufzurufen oder Dienste zu nutzen, die in einer anderen Sprache geschrieben wurden.