EMZETT.
Login

Kurz: Größere C-Programme bestehen aus vielen Dateien. Der Präprozessor und Header-Dateien halten sie zusammen.

Teil des Kurses C

Kapitel 13 von 15 im Kurs C. Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Größere C-Programme bestehen aus vielen Dateien. Der Präprozessor und Header-Dateien halten sie zusammen.

Der Präprozessor

Er arbeitet vor dem Compiler und ersetzt Text. Seine Anweisungen beginnen mit #:

AnweisungWirkung
#include <datei> / "datei"fügt den Dateiinhalt ein
#define NAME wertTextersetzung
#define MAKRO(x) ...Makro mit Argumenten
#ifdef, #ifndef, #if, #else, #endifbedingte Übersetzung
#undef NAMEDefinition aufheben
#error, #pragmaFehlermeldung, Compiler-Hinweise
#include <stdio.h>
 
#define PI 3.14159
#define QUADRAT(x) ((x) * (x))           // alle Klammern setzen!
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define VERSION "1.2"
 
#ifdef DEBUG
#define LOG(msg) printf("[DEBUG] %s\n", msg)
#else
#define LOG(msg) ((void) 0)
#endif
 
int main(void) {
    printf("%.2f %d %d\n", PI * QUADRAT(2), QUADRAT(3 + 1), MAX(4, 9));
    printf("Version %s, Datei %s, Zeile %d\n", VERSION, "x.c", __LINE__);
    LOG("nur mit -DDEBUG sichtbar");
#if defined(__linux__)
    puts("Linux");
#elif defined(_WIN32)
    puts("Windows");
#else
    puts("anderes System");
#endif
    return 0;
}

Ausgabe:

12.57 16 9
Version 1.2, Datei x.c, Zeile 16
Linux

Achtung

Makros sind reine Textersetzung ohne Typprüfung. QUADRAT(x++) wertet x++ zweimal aus. Bevorzuge static inline-Funktionen und const/enum für Konstanten.

Vordefinierte Makros: __FILE__, __LINE__, __DATE__, __func__, __STDC_VERSION__. Mit gcc -DDEBUG definierst du ein Makro von außen.

Header und Quelldateien

Teile ein Programm in Module: Die .h-Datei (Header) beschreibt die Schnittstelle (Deklarationen), die .c-Datei enthält die Umsetzung.

projekt/
├─ main.c          nutzt das Modul
├─ mathe.h         Schnittstelle (Prototypen, Typen)
└─ mathe.c         Umsetzung
/* mathe.h */
#ifndef MATHE_H            /* Include-Guard: verhindert doppeltes Einbinden */
#define MATHE_H
 
int addiere(int a, int b);
double mittelwert(const int *a, int n);
 
#endif
/* mathe.c */
#include "mathe.h"
 
int addiere(int a, int b) { return a + b; }
 
double mittelwert(const int *a, int n) {
    double s = 0;
    for (int i = 0; i < n; i++) s += a[i];
    return n ? s / n : 0;
}
/* main.c */
#include <stdio.h>
#include "mathe.h"
 
int main(void) {
    int z[] = {2, 4, 6};
    printf("%d %.1f\n", addiere(2, 3), mittelwert(z, 3));
    return 0;
}

Übersetzen:

gcc -Wall -Wextra -c mathe.c        # erzeugt mathe.o
gcc -Wall -Wextra -c main.c         # erzeugt main.o
gcc mathe.o main.o -o programm      # Linker verbindet beides
# oder alles in einem Schritt:
gcc -Wall -Wextra main.c mathe.c -o programm
  • Der Include-Guard (#ifndef ... #define ... #endif) oder #pragma once verhindert, dass ein Header mehrfach eingefügt wird
  • In Headern steht nur, was andere wissen müssen: Prototypen, typedef, struct-Definitionen, extern-Deklarationen
  • Variablen und Funktionen, die nur in einer Datei gebraucht werden, bekommen static
  • Globale Variablen für mehrere Dateien: extern int zaehler; im Header, Definition int zaehler = 0; in genau einer .c-Datei

Das Programm als Ein-Datei-Beispiel

Zur Demonstration das Zusammenspiel in einer Datei (Schnittstelle oben, Umsetzung darunter):

#include <stdio.h>
 
/* "Header"-Teil */
int addiere(int a, int b);
double mittelwert(const int *a, int n);
 
/* Verwendung */
int main(void) {
    int z[] = {2, 4, 6};
    printf("%d %.1f\n", addiere(2, 3), mittelwert(z, 3));
    return 0;
}
 
/* "Quelldatei"-Teil */
int addiere(int a, int b) { return a + b; }
double mittelwert(const int *a, int n) {
    double s = 0;
    for (int i = 0; i < n; i++) s += a[i];
    return n ? s / n : 0;
}

Ausgabe:

5 4.0

Make: Bauen automatisieren

Bei vielen Dateien übersetzt make nur, was sich geändert hat. Die Makefile:

CC = gcc
CFLAGS = -Wall -Wextra -std=c17 -g
 
programm: main.o mathe.o
	$(CC) $^ -o $@
 
%.o: %.c mathe.h
	$(CC) $(CFLAGS) -c $< -o $@
 
clean:
	rm -f *.o programm

make baut, make clean räumt auf. Größere Projekte nutzen CMake, Meson oder Ninja.

Bibliotheken

  • Die C-Standardbibliothek wird automatisch gelinkt; die Mathe-Bibliothek braucht -lm
  • Statische Bibliothek: ar rcs libmathe.a mathe.o, Verwenden mit -L. -lmathe
  • Dynamische Bibliothek (.so, .dll): gcc -shared -fPIC mathe.c -o libmathe.so
  • Fremde Bibliotheken findest du mit pkg-config --cflags --libs name

Merke

  • Der Präprozessor ersetzt Text (#include, #define, #ifdef) vor dem Compiler
  • Header (.h) enthalten die Schnittstelle, .c-Dateien die Umsetzung
  • Include-Guards verhindern doppeltes Einbinden
  • Makros sparsam und mit allen Klammern; besser static inline und const
  • make oder CMake bauen größere Projekte automatisch

Übungsaufgabe

Teile ein kleines Programm in liste.h, liste.c und main.c (verkettete Liste mit push und ausgeben) und baue es mit einer Makefile.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Dateien lesen und schreiben

Weiter: Undefiniertes Verhalten und sicheres C

Alle Kapitel: C im Überblick