INTERAKTIVE LERNSEITE · FOLIEN 11–30

Die C-Standard­bibliothek (libc)

Der unsichtbare Vermittler zwischen deinem Programm und dem Kernel. Von C vs. C++ über User- & Kernel-Modus, Systemaufrufe und Symbol Versioning bis zu Prozessen, Threads & Tracing — alles dynamisch animiert und erklärt.

bash — hello_world
$ gcc hello.c -o hello  # dynamisch gegen libc.so
$ ldd ./hello
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (GLIBC_2.34)
$ ./hello
Hello, World!
{{st.n}}{{st.l}}
Scrolle oder nutze die Navigation · 14 Kapitel
KAPITEL 01 · AUSGANGSPUNKT

Vergleich zwischen C und C++

C++ ist aus C hervorgegangen und bleibt weitgehend abwärtskompatibel — deshalb baut C++ „unter der Haube" bis heute auf der C-Welt (und der libc) auf. Die wichtigsten konzeptionellen Unterschiede:

MERKMAL
C
C++
{{row.m}}
{{row.c}}
{{row.p}}
MERKE

Trotz aller Erweiterungen greift C++ für die grundlegende Systeminteraktion weiter auf die C-Standardbibliothek zurück. new nutzt intern malloc(), std::cout endet beim write-Syscall — dazu später mehr.

KAPITEL 02 · NORMEN

Die Sprach-Standards

Beide Sprachen sind durch die ISO normiert. Diese Standards legen fest, welche Funktionen und Sprachfeatures garantiert verfügbar sind — die Basis für Portabilität. Klicke auf eine Version, um ihre wichtigsten Neuerungen zu sehen.

ISO/IEC 9899 — C-Standard
{{stdCSel.n}}{{stdCSel.iso}}

{{stdCSel.f}}

ISO/IEC 14882 — C++-Standard
{{stdCppSel.n}}{{stdCppSel.iso}}

{{stdCppSel.f}}

KAPITEL 03 · DEFINITION

Was ist die C-Standardbibliothek?

Die libc ist die Standardbibliothek der Programmiersprache C — und das Fundament, auf dem fast alle modernen Betriebssysteme und Software-Stacks aufbauen.

01 · FUNDAMENT

Basis für fast alle modernen Betriebssysteme. Auch höhere Sprachen wie Python, Rust oder Java greifen „unter der Haube" auf die libc des Host-Systems zu.

02 · DEFINITION

Die libc ist die Standardbibliothek der Sprache C: eine Sammlung fertiger, komfortabler Funktionen für Speicher, I/O, Strings und Prozesse.

03 · STANDARDISIERUNG

Definiert durch ISO C und POSIX, um Portabilität über verschiedene Plattformen hinweg zu gewährleisten.

Wo sitzt die libc? — Der Software-Stack von oben nach unten
Deine Anwendung
C · C++ · Python · Rust · Java …
↓  Funktionsaufruf (z.B. printf)
libc — die C-Standardbibliothek
Abstraktionsschicht & Syscall-Wrapper · läuft vollständig im User Space
↓  Systemaufruf (Context Switch)
Kernel — der Betriebssystem-Kern
Vollzugriff auf Hardware & Speicher · Kernel Space
Hardware
CPU · Arbeitsspeicher · Festplatte · Bildschirm · Netzwerk
KAPITEL 04 · DAS HERZSTÜCK

User Mode, Kernel Mode & der Systemaufruf

Dein Programm läuft in einer streng abgeschotteten Zone. Um Hardware zu nutzen (Bildschirm, Festplatte), muss es den Kernel um Hilfe bitten — über einen Systemaufruf. Genau hier vermittelt die libc.

USER MODE · BENUTZERMODUS
Eingeschränkte Rechte
Hier laufen alle normalen Anwendungen: dein C/C++-Programm, Browser, Editoren.
Isolierung
Kein direkter Zugriff auf Hardware oder den Speicher anderer Programme.
Fehlerwirkung
Ein Segmentierungsfehler stürzt nur dieses eine Programm ab — das System läuft weiter.
KERNEL MODE · KERNMODUS
Systemkontrolle
Hier läuft der Kern des Betriebssystems (der Kernel) samt seiner Treiber.
Vollzugriff
Darf direkt mit der Hardware sprechen und den gesamten Arbeitsspeicher verwalten.
Fehlerwirkung
Ein Fehler ist fatal und reißt das gesamte System mit: Kernel Panic / Bluescreen.
▶ Interaktiv: So reist ein printf("Hello") vom Programm zum Kernel und zurück
USER SPACE
KERNEL SPACE
⚡ HARDWARE-BARRIERE · nur per Syscall passierbar
{{scModeLabel}}
Dein Programm
printf("Hello");
libc
Puffer → write()
Kernel
Vollzugriff · schreibt auf die Hardware
{{scLabel}}
$ ./hello
{{scConsole}}
SCHRITT {{scNum}}
{{scTitle}}

{{scDesc}}

VERMITTLER

Die libc ist der Syscall-Wrapper: Sie bietet komfortable Funktionen, die intern die Systemaufrufe des Kernels auslösen. Ein printf() in C führt unter Linux letztlich zum write-Syscall. Die libc operiert dabei vollständig im User Space und bereitet nur den Kontextwechsel in den Kernel Space vor. Aus Sicht der Informationssicherheit ist diese strikte Hardware-Barriere der wichtigste Schutzmechanismus des Systems — ohne sie könnte jedes Skript den Speicher anderer Prozesse auslesen.

KAPITEL 05 · WAS DIE LIBC KANN

Kernkomponenten der libc

Die libc bündelt die grundlegenden Bausteine, die praktisch jedes Programm braucht — von Speicher über I/O bis zu Prozessen und Netzwerk.

{{c.t}}
{{c.fns}}

{{c.d}}

Kurz gesagt

Alle diese Funktionen laufen im User Space und lösen bei Bedarf Syscalls aus. Sie sind das Werkzeug, mit dem dein Code überhaupt mit dem System spricht.

KAPITEL 06 · VARIANTEN

Bekannte Implementierungen

„Die libc" gibt es nicht nur einmal. Verschiedene Projekte implementieren denselben Standard — mit unterschiedlichen Schwerpunkten für Desktop, Embedded, Mobile oder Windows.

{{im.n}} {{im.tag}}
{{im.full}}

{{im.d}}

KAPITEL 07 · DER AUSLÖSER

Dynamisches Linken

Warum ist die Version der libc so wichtig? Und warum ist ein Programm nicht ausführbar, wenn die Versionen unterschiedlich sind? — Die Antwort beginnt hier.

Die überwiegende Mehrheit der Programme wird heute dynamisch gelinkt. Der Code für Standardfunktionen wie printf oder malloc wird nicht in die ausführbare Datei geschrieben. Das Programm merkt sich nur: „Ich benötige beim Start die externe Bibliothek libc.so."

📦 hello (ausführbare Datei · ~760 KB)
dein Code printf-Code malloc-Code komplette libc eingebettet

Die gesamte libc ist fest eingebacken. Das Binary ist groß, läuft dafür überall ohne externe Abhängigkeit — auch ohne installierte libc.

📦 hello (~18 KB)
dein Code benötigt: libc.so.6
beim Start
geladen
🗄 libc.so.6
Liegt einmal auf dem System. Alle Programme teilen sich dieselbe Bibliothek — geladen zur Laufzeit vom Dynamic Linker.

Die libc definiert das Application Binary Interface (ABI) — die Schnittstelle, die festlegt, wie das Programm auf binärer Ebene mit dem System kommuniziert. Wird die libc weiterentwickelt, kommen neue Funktionen hinzu oder werden sicherheitstechnisch optimiert. Und genau daraus entsteht das Versionsproblem.

KAPITEL 08 · DIE STEMPEL

Symbol Versioning

Um das Chaos zu beherrschen, nutzt die glibc ein Konzept namens Symbol Versioning: Jede Funktion bekommt beim Kompilieren einen unsichtbaren Versionsstempel. Der Compiler verknüpft das Programm mit der exakten Version, die auf dem Entwicklersystem vorliegt — z.B. memcpy@GLIBC_2.14.

Die Symbole, die hello_world anfordert — jedes mit eigenem Versionsstempel:
__libc_start_mainGLIBC_2.34
printfGLIBC_2.2.5
mallocGLIBC_2.2.5
strdupGLIBC_2.2.5
🎯

Die höchste geforderte Version — hier GLIBC_2.34 — bestimmt die harte Mindestvoraussetzung für das Betriebssystem.

Nachprüfbar mit ldd -v ./hello_world:
$ ldd -v ./hello_world
Version information:
./hello_world:
libc.so.6 (GLIBC_2.34) => /lib/x86_64-linux-gnu/libc.so.6
libc.so.6 (GLIBC_2.2.5) => /lib/x86_64-linux-gnu/libc.so.6
GLIBC_2.2.5

Die historische Basisversion für die 64-Bit-Architektur (x86_64, seit 2002). Funktionen, die sich funktional nie ändern mussten, sind immer noch unter diesem Label registriert.

GLIBC_2.34

Das Programm ruft mindestens eine Funktion auf, die in 2.34 überarbeitet wurde. Typischer Kandidat: __libc_start_main, dessen Implementierung angepasst wurde, um POSIX-Threads direkt im Kern zu integrieren.

KAPITEL 09 · INTERAKTIV

Warum ein Programm den Dienst verweigert

Der Dynamic Linker prüft beim Start, ob das System alle geforderten Symbolversionen liefern kann. Probiere es aus: Stelle ein, welche glibc-Version das Programm braucht und welche das Zielsystem anbietet.

Programm wurde gebaut fürGLIBC_{{reqVer}}
Zielsystem bietetGLIBC_{{sysVer}}
user@zielsystem:~$ ./hello_world
Hello, World!
./hello_world: /lib/x86_64-linux-gnu/libc.so.6:
version `GLIBC_{{reqVer}}' not found (required by ./hello_world)
✓ Läuft. Das System bietet eine gleich hohe oder neuere glibc — dank Abwärtskompatibilität findet der Dynamic Linker alle geforderten Symbole.
✗ Startet nicht. Dem älteren System fehlt das „Vokabular" der neueren Version. Der Abbruch passiert sofort — bevor eine einzige Zeile Programmcode läuft.
Altes Programm → neues System

Läuft meist problemlos. Die glibc-Entwickler nehmen Abwärtskompatibilität ernst: Alte Symbole bleiben unter ihrem alten Versionsstempel erhalten.

Neues Programm → altes System

Scheitert. Das Programm fordert Symbole an, die es erst seit einer neueren Version gibt. Der Dynamic Linker kann sie nicht bedienen und bricht ab.

VERTRAG

Man kann es sich wie einen strengen Vertrag vorstellen: Der Compiler schreibt die Version der lokal vorhandenen libc als harte Mindestanforderung in den Header des Programms. Ein älteres OS kann diesen Vertrag nicht erfüllen — und verweigert die Ausführung lieber komplett, statt im Betrieb mit einem unvorhersehbaren Speicherfehler abzustürzen.

KAPITEL 10 · UNTER DER HAUBE

C++ baut auf der libc auf

„Aber wir programmieren ja in C++ und nicht in C?" — Ja, absolut. Und trotzdem verlässt sich C++ massiv auf die libc als fundamentales Fundament.

Auch wenn C++ seine eigene, mächtige Standardbibliothek mitbringt (libstdc++ beim GCC, libc++ bei Clang) — die libc fungiert unter der Haube weiterhin als Brücke zum Betriebssystem. Viele High-Level-Konzepte rufen intern libc-Funktionen auf, statt das Rad neu zu erfinden:

SPEICHER
new · delete
malloc() · free()

Ein Objekt auf dem Heap mit new fordert Speicher intern über malloc() an; delete ruft free().

EIN- / AUSGABE
std::cout << "…"
write-Syscall

Durchläuft die Pufferung der C++-Streams, endet aber tief im System bei den I/O-Funktionen der libc — und schließlich beim write.

MULTITHREADING
std::thread
pthread_create()

Ein std::thread ist unter Linux ein Wrapper für pthread_create(). Die POSIX-Threads sind integraler Bestandteil der glibc.

Fazit — jedes kompilierte C++-Programm wird fast immer gegen zwei Bibliotheken gelinkt:
dein_programm
C++-Binary
gelinkt
gegen →
libstdc++.so — Klassen, Templates, STL-Container
libc.so — grundlegende Systeminteraktion
FOLGE

Alle Probleme rund um Versionierung und Informationssicherheit, die für die libc gelten, schlagen potenziell voll auf C++-Programme durch — sobald diese unter der Haube auf die libc zugreifen.

AUFGABENBLATT 1

Parallelisierung: Prozesse vs. Threads

Dieselbe Matrixmultiplikation, zweimal parallelisiert — einmal mit Threads, einmal mit Prozessen. Der Unterschied liegt im Speichermodell und wird als Laufzeit messbar.

THREADS · geteilter Adressraum

Threads laufen innerhalb eines Prozesses und teilen sich denselben Adressraum: Heap, globale Variablen, geöffnete Dateien.

→ Kommunikation sehr schnell (direkter Speicherzugriff)
→ erfordert Synchronisation gegen Race Conditions
geringer Overhead beim Erstellen
PROZESSE · isolierter Adressraum

Prozesse haben einen eigenen, isolierten Adressraum. Kommunikation nur über explizite Mechanismen (IPC): Dateien, Pipes, Shared Memory.

→ starke Isolation, hohe Fehlertoleranz
→ IPC nötig, kein direkter Zugriff
höherer Overhead: fork() kopiert Kontext (Copy-on-Write)
MATRIXMULTIPLIKATION · warum parallelisierbar?
C[i][j] = Σl  A[i][l] · B[l][j]

Jede Zeile der Ergebnismatrix C lässt sich unabhängig von allen anderen berechnen. Genau das macht die Multiplikation ideal für Parallelisierung — man verteilt die Zeilen einfach auf mehrere Threads oder Prozesse.

▶ Interaktiv: beide Verfahren berechnen dieselbe 8×8-Ergebnismatrix. Jede Farbe = ein Worker (Zeilenblock).
Worker:
Threadsliest A&B: {{mReadsT}}×
1× gemeinsam eingelesen · geteilter Speicher
Prozesseliest A&B: {{mReadsP}}×
jeder Prozess liest A & B selbst von Platte
🗄 liest A & B von Festplatte …
{{w.label}} · {{w.rows}}

Beobachtung: Bei den Prozessen startet die Berechnung erst nach der Datei-I/O-Phase — dieser Overhead fehlt bei Threads, die die Daten bereits gemeinsam im Speicher halten. Bei größeren Matrizen relativiert sich das, da dann die reine Rechenzeit dominiert.

▶ Interaktiv: Wie viel bringt mehr Parallelität? — das Amdahlsche Gesetz
Obergrenze {{maxS}}×
{{b.label}}
{{b.n}}
Anzahl Worker N · Speedup S(N)
S = 1 / ((1−p) + p/N)
parallelisierbar (p){{aPpct}}%
hervorgehobene Worker-Zahl:
Speedup bei N = {{aN}}
{{curS}}×

Der sequenzielle Rest (1−p) deckelt den Speedup: Bei p = {{aPpct}}% ist selbst mit unendlich vielen Workern bei {{maxS}}× Schluss. Genau deshalb bringt das Verdoppeln der Prozesse oder Threads irgendwann kaum noch etwas.

Der vollständige Quellcode der Aufgabe — klicke zum Aufklappen:
{{f.gutter}}
{{f.el}}
AUFGABENBLATT 2

Tracing: die zwei Beobachtungsebenen

Dasselbe kleine hello_world ruft eine Reihe von libc-Funktionen auf. Zwei Werkzeuge machen sichtbar, was dabei passiert: ltrace zeigt die libc-Ebene, strace die darunterliegende Kernel-Syscall-Ebene.

{{f.gutter}}
{{f.el}}
▶ Interaktiv: dasselbe Programm durch zwei verschiedene Linsen betrachtet
{{traceTitle}}
{{l.f}} {{l.a}} {{l.retDisp}}
EBENE 1 · ltrace

Setzt Breakpoints in der PLT und macht jeden Aufruf einer libc-Funktion sichtbar — malloc, printf, strcpy. Man sieht, was dein Code von der Bibliothek verlangt.

EBENE 2 · strace

Nutzt ptrace und beobachtet die eigentlichen Kernel-Syscalls — brk, mmap, write. Man sieht, was die libc am Ende beim Betriebssystem anfordert.

Zuordnung: libc-Aufruf → ausgelöster Syscall
{{m.lc}} → {{m.sy}} {{m.note}}
▶ Interaktiv: welchen Syscall löst malloc() aus?
Angeforderte Größe{{mallocKB}} KB
1 KB · brk128 KB · Schwelle512 KB · mmap
char *p = malloc({{mallocKB}} * 1024);
→ Kernel-Syscall: {{mallocSys}}

{{mallocDesc}}

REFERENZ

Fachwortglossar

Alle wichtigen Begriffe aus der Vorlesung und den Aufgabenblättern — kompakt erklärt. Filtere nach Kategorie oder suche direkt nach einem Stichwort.

{{gCount}} Begriffe
{{g.t}} {{g.c}}
{{g.de}}

{{g.d}}

SELBSTTEST

Wissens-Quiz

14 Fragen quer durch alle Kapitel. Wähle eine Antwort — die Erklärung erscheint sofort.

FRAGE {{qNum}}{{qScore}} richtig
{{qd.q}}
{{qCorrectLabel}}

{{qExplain}}

DEIN ERGEBNIS
{{qPct}}%
{{qScore}} von {{qTotal}} Fragen richtig

{{qResultMsg}}