📘 Kapitel 7 · Unit 36 von 36

Mehrere Quelldateien
Header · .h und .c · Modularisierung

Header & Quelldateien · Include Guards · Separate Kompilierung · Projektstruktur

36 / 36 Units ✓
Ömer
Kapitel 7: Praxis & Fortgeschritten ~100 Min. Theorie + Simulator + Quiz + Spickzettel Letztes Kapitel!
Ömer
Ömer sagt:

Das ist es — die letzte Unit des gesamten Kurses! 🎉 Und wir schließen mit einem der wichtigsten Konzepte echter Projekte ab: Wie du ein Programm in mehrere Dateien aufteilst. Das unterscheidet Hobby-Code von professioneller Entwicklung. Du schaffst das!

1. Warum mehrere Dateien?

Stell dir vor, ein Programm hat 10.000 Zeilen Code — alles in einer einzigen .c-Datei. Niemand kann das lesen, niemand kann darin etwas finden, und Teamarbeit ist unmöglich. Die Lösung: Modularisierung.

❌ Problem: Eine riesige Datei

  • Unlesbar und schwer wartbar
  • Teamarbeit ist nicht möglich
  • Kein Code kann wiederverwendet werden
  • Jede kleine Änderung erfordert vollständige Neukompilierung

✅ Lösung: Mehrere Module

  • Bessere Übersicht — jede Datei hat eine klare Aufgabe
  • Teamarbeit: jeder arbeitet an seinen Dateien
  • Wiederverwendung: Module in anderen Projekten nutzen
  • Schnellere Kompilierung: nur geänderte Dateien neu kompilieren

Analogie

Ein Buch hat Kapitel — ein Programm hat Module. Kein Leser möchte ein 500-seitiges Buch ohne Gliederung lesen, genauso wenig möchte ein Entwickler eine Mammutdatei durchsuchen.

2. Header-Dateien (.h) vs. Quelldateien (.c)

Das Kernprinzip: Was gibt es? kommt in die .h-Datei, Wie funktioniert es? kommt in die .c-Datei.

DateiEnthältBeispiel
.h HeaderDeklarationen, Typen, Makros — die Schnittstelle (Interface)int addiere(int a, int b);
.c QuelleDefinitionen (Implementierung) — der echte Codeint addiere(int a, int b) { return a+b; }

Header = "Was gibt es?"

Die .h-Datei ist wie ein Inhaltsverzeichnis oder eine Speisekarte: Sie listet auf, welche Funktionen, Typen und Konstanten ein Modul bereitstellt — ohne zu verraten, wie sie funktionieren.

Source = "Wie funktioniert es?"

Die .c-Datei enthält die eigentliche Implementierung: den kompletten Funktionsrumpf mit dem echten Code. Wer das Modul nutzt, muss diesen Teil nicht kennen.

Goldene Regel

Declare in .h — Define in .c

Deklarationen (Prototypen) gehören in den Header. Definitionen (Implementierungen) gehören in die Quelldatei.

3. Include Guards — unverzichtbar in jedem Header

Was passiert, wenn zwei verschiedene Dateien denselben Header einbinden? Ohne Schutz: doppelte Deklaration — Compilerfehler! Die Lösung sind Include Guards:

rechner.hC
#ifndef RECHNER_H
#define RECHNER_H

// Deklarationen — nur einmal eingebunden, egal wie oft #include
int addiere(int a, int b);
int subtrahiere(int a, int b);

#endif  /* RECHNER_H */

Wie Include Guards funktionieren

  1. #ifndef RECHNER_H — "Wenn RECHNER_H noch NICHT definiert ist …"
  2. #define RECHNER_H — "… dann definiere es jetzt …"
  3. Alle Deklarationen dazwischen werden einmal verarbeitet
  4. #endif — Ende des bedingten Blocks
  5. Beim zweiten #include der selben Datei: RECHNER_H ist schon definiert → der gesamte Block wird übersprungen

Konvention für Guard-Namen

Dateiname in Großbuchstaben, Punkte durch Unterstriche ersetzen:

  • rechner.hRECHNER_H
  • string_utils.hSTRING_UTILS_H
  • mein_modul.hMEIN_MODUL_H

4. Vollständiges 3-Datei-Beispiel

Das Standardbeispiel: ein rechner-Modul mit Header, Implementierung und Hauptprogramm:

rechner.hC
#ifndef RECHNER_H
#define RECHNER_H

int addiere(
  int a, int b);
int subtrahiere(
  int a, int b);
float dividiere(
  float a,
  float b);

#endif
rechner.cC
#include "rechner.h"

int addiere(
  int a, int b) {
  return a + b;
}
int subtrahiere(
  int a, int b) {
  return a - b;
}
float dividiere(
  float a,
  float b) {
  return b != 0
    ? a/b : 0;
}
main.cC
#include <stdio.h>
#include "rechner.h"

int main() {
  printf(
    "5+3=%d\n",
    addiere(
      5, 3));
  printf(
    "10/4=%.2f\n",
    dividiere(
      10, 4));
  return 0;
}
▶ Ausgabe nach gcc main.c rechner.c -o rechner && ./rechner
5+3=8 10/4=2.50

5. Kompilierung mehrerer Dateien

Beim Kompilieren müssen alle .c-Dateien angegeben werden (nicht die .h-Dateien — die werden automatisch per #include eingebunden):

TerminalSHELL
# Alle .c-Dateien auf einmal kompilieren
gcc main.c rechner.c -o rechner

# Oder: Separat kompilieren (nur geänderte Dateien nötig)
gcc -c rechner.c -o rechner.o   # erzeugt Objektdatei
gcc -c main.c    -o main.o      # erzeugt Objektdatei
gcc main.o rechner.o -o rechner # linkt zusammen

Was macht gcc -c?

Der Flag -c bedeutet "nur kompilieren, nicht linken". Das Ergebnis ist eine Objektdatei (.o) — maschinennaher Code, aber noch kein ausführbares Programm. Erst der Linker verbindet alle .o-Dateien zu einem Programm.

Vorteil separater Kompilierung

Bei 10 Dateien und einer Änderung in nur einer: nur diese eine Datei neu kompilieren, dann neu linken. Bei großen Projekten spart das enorm viel Zeit. Makefiles automatisieren genau das.

Makefiles — der nächste Schritt

Ein Makefile definiert Regeln für die Kompilierung: welche Dateien voneinander abhängen, in welcher Reihenfolge kompiliert wird. Statt langen GCC-Befehlen reicht dann ein einfaches make. Das Arbeitsblatt zu dieser Unit enthält ein vollständiges Makefile-Beispiel!

6. Typische Projektstruktur

So sieht ein gut organisiertes C-Projekt mit mehreren Modulen aus:

projekt/
  ├── main.c          // Hauptprogramm — Einstiegspunkt
  ├── rechner.h       // Modul rechner: Header (Schnittstelle)
  ├── rechner.c       // Modul rechner: Implementierung
  ├── utils.h         // Hilfsfunktionen: Header
  ├── utils.c         // Hilfsfunktionen: Implementierung
  └── Makefile        // optional: Kompilierung automatisieren

Jedes Modul besteht aus genau einem Paar: modul.h und modul.c. Das macht die Struktur vorhersagbar und leicht navigierbar.

7. Was kommt in den Header? Was in die .c?

Gehört in .h (Header)Gehört in .c (Quelldatei)
Funktionsdeklarationen (Prototypen)Funktionsdefinitionen (mit Implementierung)
typedef, struct, enum DefinitionenLokale Variablen und Hilfsfunktionen
#define Konstanten und Makrosstatic Funktionen (nur intern)
extern VariablendeklarationenGlobale Variablendefinitionen
Include Guards (#ifndef#endif)#include "eigener_header.h"

⚠️ NIEMALS Variablen in .h definieren!

Wenn du int zaehler = 0; in eine .h-Datei schreibst und diese Header von drei .c-Dateien eingebunden wird, entstehen drei Definitionen derselben Variable — das führt zu einem Linker-Fehler (multiply defined symbol).

Richtig: In der .h-Datei nur extern int zaehler; (Deklaration), in genau einer .c-Datei int zaehler = 0; (Definition).

8. Häufige Fehler

FehlerFolgeLösung
Variable in .h definierenLinker-Fehler: multiply definedextern in .h, Definition in .c
Include Guard vergessenDoppelte Deklaration, Compilerfehler#ifndef/#define/#endif hinzufügen
Zirkuläre Includes (a.h → b.h → a.h)Endlosschleife im PräprozessorForward Declarations verwenden
Falscher Pfad bei #include "..."Compilerfehler: file not foundPfad relativ zur aktuellen Datei angeben
.h-Datei im GCC-Befehl angebenCompilerfehler oder unerwartetes VerhaltenNur .c-Dateien kompilieren; .h wird per #include eingebunden
#include <...> statt "..." für eigene DateienDatei wird nicht gefunden"..." für eigene, <...> für Systemheader

✅ Checkliste für jeden neuen Header

  1. Include Guard hinzufügen (#ifndef DATEI_H / #define DATEI_H / #endif)
  2. Nur Deklarationen, keine Definitionen
  3. Keine Variablendefinitionen (nur extern)
  4. In der zugehörigen .c-Datei den eigenen Header einbinden: #include "datei.h"
  5. Alle .c-Dateien im GCC-Befehl angeben

⚡ Code-Simulator

Hinweis zur Simulation: Echte Mehr-Datei-Projekte können im Browser-Simulator nicht kompiliert werden — dafür braucht es GCC auf deinem Rechner. Der Simulator unten zeigt den Inhalt von main.c mit den Funktionen direkt eingebunden, damit du die Logik testen kannst. Auf deinem PC würdest du alle drei Dateien anlegen und mit gcc main.c rechner.c -o rechner kompilieren.

Die drei Dateien des rechner-Projekts:

rechner.h — Header (Schnittstelle)C
#ifndef RECHNER_H
#define RECHNER_H

int   addiere(int a, int b);
int   subtrahiere(int a, int b);
float dividiere(float a, float b);

#endif  /* RECHNER_H */
rechner.c — ImplementierungC
#include "rechner.h"

int addiere(int a, int b)          { return a + b; }
int subtrahiere(int a, int b)     { return a - b; }
float dividiere(float a, float b) { return b != 0 ? a/b : 0; }
main.c — HauptprogrammC
#include <stdio.h>
#include "rechner.h"   // Eigene Header mit ""

int main() {
    printf("5 + 3 = %d\n",      addiere(5, 3));
    printf("10 - 4 = %d\n",     subtrahiere(10, 4));
    printf("10 / 4 = %.2f\n",   dividiere(10, 4));
    return 0;
}

Im Simulator unten kannst du die Logik der Funktionen direkt testen — alle drei Dateien sind zusammengeführt, damit printf simuliert werden kann:

C Simulator – Unit 36 (main.c + rechner.c kombiniert)
▶ Ausgabe
– Klicke auf AUSFÜHREN –
Ömer
Ömer sagt:

Im echten Projekt liegen diese Teile in separaten Dateien. Der Simulator zeigt dir, dass die Funktionslogik korrekt ist. Probiere eigene Funktionen aus — ändere die Zahlen oder füge neue Berechnungen hinzu!

🎯 Wissens-Quiz

Frage 1
Was enthält eine .h Header-Datei hauptsächlich?
ADie vollständige Implementierung aller Funktionen
BDeklarationen — die Schnittstelle (Interface) des Moduls
CNur Kommentare und Dokumentation
DDen main()-Einstiegspunkt
Frage 2
Was ist der Unterschied zwischen Deklaration und Definition?
AEs gibt keinen Unterschied, beide Begriffe bedeuten dasselbe
BDeklaration enthält den Funktionsrumpf, Definition nur den Prototyp
CDeklaration = Prototyp (ohne Rumpf), Definition = vollständige Implementierung mit {}
DDeklarationen sind in .c, Definitionen in .h
Frage 3
Wozu dienen Include Guards (#ifndef / #define / #endif)?
ASie beschleunigen die Kompilierung
BSie schützen den Header vor unbefugtem Zugriff
CSie verhindern, dass derselbe Header mehrfach eingebunden wird (doppelte Deklaration)
DSie sind nur für C++, in C nicht notwendig
Frage 4
Welcher GCC-Befehl kompiliert main.c und utils.c zu einem Programm namens "prog"?
Agcc main.c utils.h -o prog
Bgcc main.c utils.c -o prog
Cgcc -compile main.c utils.c prog
Dgcc *.h *.c -o prog
Frage 5
Was ist falsch an: int x = 5; direkt in einer .h Datei (ohne extern)?
AGar nichts — das ist völlig in Ordnung
BDie Variable wird mehrfach definiert, wenn mehrere .c-Dateien den Header einbinden — Linker-Fehler
CVariablen dürfen in .h-Dateien nicht initialisiert werden
Dint ist kein gültiger Typ in Header-Dateien
Frage 6
Welche Konvention gilt für Include-Guard-Namen?
ABeliebiger Name — es gibt keine Konvention
BDateiname in Kleinbuchstaben: rechner_h
CDateiname in Großbuchstaben mit Unterstrichen: RECHNER_H
DImmer GUARD_ als Präfix: GUARD_RECHNER
Frage 7
Was ist der Unterschied zwischen #include "rechner.h" und #include <stdio.h>?
AKein Unterschied — beide Schreibweisen sind identisch
B"..." sucht zuerst im aktuellen Verzeichnis (eigene Dateien); <...> sucht in Systempfaden
C"..." ist für C++, <...> für C
D<...> ist veraltet und sollte nicht mehr verwendet werden
Frage 8
Was ist ein Vorteil separater Kompilierung mit gcc -c?
ADas Programm läuft schneller zur Laufzeit
BMan benötigt keine Header-Dateien mehr
CNur geänderte Dateien müssen neu kompiliert werden — spart Zeit bei großen Projekten
DMan kann auf Include Guards verzichten

📋 Spickzettel

.h vs .c — Grundregel
.h HeaderWas gibt es? (Schnittstelle)
.c QuelleWie funktioniert es? (Implementierung)
Declare in .hPrototypen, Typen, Makros
Define in .cFunktionsrümpfe, Variablen
Include Guard Vorlage
#ifndef DATEI_HBeginn des Guards
#define DATEI_HMakro setzen
/* Inhalt */Deklarationen hier
#endifEnde des Guards
GCC Mehrere Dateien
gcc a.c b.c -o progAlles auf einmal
gcc -c modul.c -o modul.oNur kompilieren
gcc a.o b.o -o progObjektdateien linken
makeVia Makefile
Häufige Fehler
int x = 5; in .h→ multiply defined!
Kein Include Guard→ doppelte Deklaration
<header> statt "header"→ file not found
.h im GCC-Befehl→ nur .c angeben!

Vollständiges Mini-Projekt (3 Dateien)

rechner.hHEADER
#ifndef RECHNER_H
#define RECHNER_H
int   addiere(int a, int b);
float dividiere(float a, float b);
#endif
rechner.cSOURCE
#include "rechner.h"
int   addiere(int a, int b)          { return a + b; }
float dividiere(float a, float b) { return b != 0 ? a/b : 0; }
KompilierenSHELL
gcc main.c rechner.c -o rechner

✅ Checkliste Unit 36 — und des gesamten Kurses!

  • Ich verstehe den Unterschied zwischen .h und .c Dateien
  • Ich kann Include Guards korrekt schreiben
  • Ich weiß, was in den Header gehört und was in die Quelldatei
  • Ich kann mit GCC mehrere .c-Dateien zu einem Programm kompilieren
  • Ich kenne die häufigsten Fehler (Variable in .h, kein Include Guard)
  • Ich habe alle 36 Units von "Ömer erklärt C" abgeschlossen! 🎉
Ömer
Ömer sagt:

Du hast es geschafft! 36 Units, von den Grundlagen bis zu professioneller Modularisierung. Jetzt kennst du die Bausteine echter C-Projekte. Ob du weiter in C tiefer gehst, zu C++ wechselst oder ein eigenes Projekt startest — du hast das Fundament. Glückwunsch und viel Erfolg! 🏆