C - Der Systemnahe Standard
C ist seit Jahrzehnten der etablierte Standard für systemnahe Programmierung – nicht ohne Grund. Die Sprache bietet eine einfache Syntax und ein bewährtes Ökosystem.
Doch trotz dieser Stärken gibt es fundamentale Probleme:
- Speicherverwaltungsfehler sind häufig und können zu Sicherheitslücken führen
- Mangelnde Typsicherheit durch schwache Typisierung sorgen für schwer zu findende Fehler
- Null-Referenzen haben laut Tony Hoare Schäden in Milliardenhöhe verursacht
- Parallelität ist extrem schwierig korrekt zu implementieren
C ist eine standardisierte Sprache, für die es zahlreiche Compiler-Implementierungen gibt. Der C-Standard legt die Syntax und das Verhalten der Sprache fest, lässt jedoch bewusst einige Aspekte offen. Diese Aspekte bezeichnet man als undefiniertes Verhalten.
Häufige Fehler in C
Abschnitt betitelt „Häufige Fehler in C“Speicherverwaltung
Abschnitt betitelt „Speicherverwaltung“int* ptr = malloc(sizeof(int));*ptr = 42;free(ptr);*ptr = 10; // Use-after-free → UB!Schwache Typisierung
Abschnitt betitelt „Schwache Typisierung“int x = -1;unsigned int y = x; // Implizite Konvertierung → große positive ZahlNull Pointer
Abschnitt betitelt „Null Pointer“char* str = NULL;printf("%s", str); // Crash oder UB$ tcc -run main.c # tcc ist ein C-KompilerSegmentation fault (core dumped) tcc -run main.cDurch das Verstehen der C-Probleme wird klar, warum moderne Sprachen wie Rust entwickelt wurden:
Memory Safety ohne Garbage Collection
-
Starke Typisierung
-
Absence of Null (Option-Types)
-
Sichere Parallelität
-
Bewusste Sprachenwahl für neue Projekte
-
Bessere Code-Qualität durch Problembewusstsein
-
Sicherheitskritische Systeme richtig entwickeln
-
Technische Schulden in Legacy-Code identifizieren
Die Industrie von fehleranfälligen zu sicheren systemnahen Programmiersprachen zu bewegen, ohne Performance-Einbußen oder die Kontrolle zu verlieren, die C bietet.
Undefined Behaviour
Abschnitt betitelt „Undefined Behaviour“Compiler Unterschiede
Abschnitt betitelt „Compiler Unterschiede“int main() { int i = 2; int result = --i + i++; return 0;}- gcc https://godbolt.org/z/oYE6zvP9h (result=3)
- clang https://godbolt.org/z/bxx7YPPvb (result=2)
Buffer Overflow
Abschnitt betitelt „Buffer Overflow“Ein Buffer Overflow tritt auf, wenn mehr Daten in einen fest dimensionierten Speicherbereich (Buffer) geschrieben werden, als dieser fassen kann. Dies führt dazu, dass benachbarte Speicherbereiche überschrieben werden, was zu unvorhersehbarem Verhalten oder Sicherheitslücken führen kann.
#include <stdio.h>#include <string.h>int main() { char name[5]; printf("Enter your name: "); gets(name); printf("Hello, %s!\n", name); return 0;}Use after free
Abschnitt betitelt „Use after free“- Allokieren von Speicher
- Schreiben in den Speicher
- Freigeben des Speichers
- Lesen aus dem Speicher -> Fehlerhaft
#include <stdio.h>#include <stdlib.h>int main() { int *ptr = (int *)malloc(sizeof(int)); // TODO check allocation success *ptr = 42; printf("ptr: %d\n", *ptr); free(ptr); // Pointer is dangling printf("ptr after free: %d\n", *ptr); return 0;}Double Free
Abschnitt betitelt „Double Free“- Speicher wird allokiert
- Pointer wird an free uebergeben
- Speicher is freigegeben, Pointer existiert weiter
- Pointer wird erneut an free uebergeben -> Welcher Speicher wird bereinigt?
#include <stdio.h>#include <stdlib.h>int main() { int *ptr = (int *)malloc(sizeof(int)); free(ptr); free(ptr); return 0;}Invalid Index
Abschnitt betitelt „Invalid Index“- Index ist groesser als array
- Value enthaelt falsche Daten
#include <stdio.h>int main() { int arr[5] = {10, 20, 30, 40, 50}; int value = arr[6]; printf("Value: %d\n", value); return 0;}Divide by Zero
Abschnitt betitelt „Divide by Zero“- Undefinded
- Manche Programme brechen ab, manche arbeiten mit falschem Ergebnis weiter
#include <stdio.h>int main() { int numerator = 10; int denominator = 0; int result = numerator / denominator; printf("Result: %d\n", result); return 0;}Dangling Pointer
Abschnitt betitelt „Dangling Pointer“- Value in einem lokalen Scope
- Dereferenzieren des Values im lokalen Scope
- Referenz (Pointer) wird returned
- Value existiert nicht mehr, Pointer schon
- Pointer zeig auf fehlerhaften Speicher
int *getDanglingPointer() { int value = 42; return &value;}
int main() { int *ptr = getDanglingPointer(); *ptr = 10; // Unsicher welcher Speicher beschrieben wird printf("%d", *ptr); return 0;}C: Effizienz zum Preis der Sicherheit
Abschnitt betitelt „C: Effizienz zum Preis der Sicherheit“Aus den vorhergegangenen Beispielen wird ersichtlich, dass gültige C Programme zu einigen Probelmen führen können.
Warum moderne Alternativen entstehen
Abschnitt betitelt „Warum moderne Alternativen entstehen“Rust
- Memory Safety