Menu

Der C-Präprozessor: #include, #define und was vor der Übersetzung läuft

Der Präprozessor bearbeitet deinen Quelltext, bevor der Compiler ihn je sieht - er fügt Header ein, ersetzt Makros und schaltet Code an oder aus. Hier ist die ganze Familie der Direktiven und wie man sich ihre Ausgabe ansieht.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Jede C-Datei, die du je kompiliert hast, begann mit einer Zeile wie #include <stdio.h>, und diese Zeile ist kein C. Sie ist eine Anweisung an ein eigenes Programm - den Präprozessor -, das zuerst läuft, deinen Quelltext umschreibt und das Ergebnis an den Compiler reicht.

Diese zweistufige Trennung zu verstehen erklärt viel am Verhalten von C: warum Header eingefügt statt importiert werden, warum ein schlechtes Makro einen Fehler in einer völlig unverdächtigen Zeile erzeugt, und warum dieselbe Quelldatei auf verschiedenen Systemen zu verschiedenen Programmen übersetzt werden kann.

Was vor der Übersetzung tatsächlich passiert

Eine .c-Datei zu kompilieren ist kein einzelner Schritt. Grob gesagt sind es vier:

  1. Vorverarbeitung - jede Zeile befolgen, die mit # beginnt, und daraus einen großen expandierten Quelltext erzeugen (eine Übersetzungseinheit).
  2. Übersetzen - diesen Text in Assembler und dann in eine Objektdatei verwandeln.
  3. Assemblieren - Maschinencode erzeugen.
  4. Linken - die Objektdateien mit den Bibliotheken zu einer ausführbaren Datei verbinden.

Der Präprozessor arbeitet rein auf Text. Er weiß nicht, was eine Variable ist, was ein Typ ist oder ob deine Klammern ausgeglichen sind. Er sieht Zeichen und Token, ersetzt einige davon durch andere und macht weiter. Alles Seltsame an Makros folgt aus dieser einen Tatsache.

Wenn der Compiler dieses Programm liest, gibt es nirgends mehr ein GREETING. Der Präprozessor hat es bereits durch das Literal "Hallo aus einem Makro" ersetzt, genau so, als hättest du es getippt.

Die Familie der Direktiven

Jede Präprozessor-Direktive beginnt mit # als erstem Zeichen außer Leerraum in ihrer Zeile. Es gibt kein Semikolon, und die Direktive endet am Zeilenende, sofern du sie nicht mit einem Rückstrich fortsetzt.

DirektiveWas sie tut
#includeDen Inhalt einer anderen Datei einfügen
#defineEin Makro definieren (eine Textersetzung)
#undefEine Makrodefinition entfernen
#ifdef, #ifndefDen folgenden Code nur behalten, wenn ein Makro (nicht) definiert ist
#if, #elif, #else, #endifCode anhand eines konstanten Ausdrucks behalten
#errorDie Übersetzung mit einer Meldung stoppen
#pragmaCompilerspezifische Anweisung, etwa #pragma once
#lineDie gemeldete Zeilennummer ändern (selten)

Drei davon bekommen eigene Seiten: Makros behandelt #define ausführlich, bedingte Übersetzung die #if-Familie und Header-Dateien, wie #include ein Programm aus mehreren Dateien strukturiert.

#include: spitze Klammern gegen Anführungszeichen

#include tut genau eine Sache: Es ersetzt seine eigene Zeile durch den gesamten Inhalt der genannten Datei. Diese eingebundene Datei wird selbst vorverarbeitet, ihre eigenen #include-Zeilen expandieren also ebenfalls.

#include <stdio.h>     /* die System-Include-Verzeichnisse durchsuchen */
#include "config.h"    /* zuerst das Verzeichnis dieser Datei durchsuchen */

Der Unterschied ist die Suchreihenfolge:

  • <spitze Klammern> schauen in die Standard-Include-Verzeichnisse des Compilers - /usr/include, die eigenen Ordner der Werkzeugkette, plus alles, was du mit -I hinzufügst. Das ist für Bibliotheksheader.
  • "Anführungszeichen" schauen zuerst in das Verzeichnis der einbindenden Datei und weichen dann auf dieselbe Liste wie die spitzen Klammern aus. Das ist für selbstgeschriebene Header.

Beide Formen funktionieren auf den meisten Compilern für beide Arten von Headern, aber die Konvention trägt Bedeutung: Spitze Klammern sagen „das ist der Header von jemand anderem", Anführungszeichen sagen „das ist meiner". Sie zu verwechseln ist der Weg, auf dem ein Projekt einen veralteten Systemheader statt seines eigenen einbindet.

Weil das Einbinden ein Einfügen von Text ist, fügt das zweimalige Einbinden desselben Headers ihn zweimal ein - weshalb Header einen Include-Schutz brauchen. Das ist das Erste, was die Seite Header-Dateien behebt.

#define: Textersetzung, sonst nichts

#define NAME Ersatz sagt dem Präprozessor: Von hier an, wo immer das Token NAME auftaucht, setze stattdessen Ersatz ein.

Beachte, was hier nicht passiert. MAX_USERS hat keinen Typ. Es wird nirgends gespeichert. Es kann in keinem Debugger inspiziert werden. Es ist eine Suchen-und-Ersetzen-Regel, und nach der Vorverarbeitung enthält das Programm buchstäblich printf("%s erlaubt %d Nutzer\n", "Coddy", 100);.

Das bedeutet auch, dass die Ersetzung blind ist. Das hier kompiliert und tut etwas Überraschendes:

#define SIZE 5 + 1

int arr[SIZE];          /* in Ordnung: int arr[5 + 1]; */
int total = SIZE * 2;   /* 5 + 1 * 2 == 7, nicht 12 */

Die Lösung - Klammern um alles - ist die zentrale Regel der Seite Makros, zusammen mit Makros, die Argumente nehmen.

Ein #define ohne Ersatztext definiert den Namen als „vorhanden, aber leer". Das ist als Ersetzung nutzlos und als Flag unverzichtbar:

#define DEBUG          /* definiert, expandiert zu nichts */

Code kann dann mit #ifdef fragen, ob DEBUG existiert.

Die Ausgabe des Präprozessors ansehen

Der beste Weg, Gespür aufzubauen, ist sich anzusehen, was der Präprozessor tatsächlich erzeugt hat. gcc -E hält nach der Vorverarbeitung an und gibt das Ergebnis aus:

gcc -E hello.c

Bei einer Datei, die <stdio.h> einbindet, sind das je nach System 700 bis 30.000 Zeilen - fast alles davon der Inhalt des Headers. Um nur deinen Teil zu sehen, nimm das Ende:

gcc -E hello.c | tail -20

Probiere es an einer Datei wie dieser:

#define SQUARE(x) ((x) * (x))
#define LIMIT 10

int main(void) {
    int n = SQUARE(LIMIT);
    return n;
}

Das Ende der Ausgabe zeigt:

int main(void) {
    int n = ((10) * (10));
    return n;
}

Jedes Makro ist fort; nur ersetzter Text bleibt. Wenn sich ein Makro falsch verhält, sagt dir dieser Befehl in Sekunden, warum, und er schlägt das Raten. Zwei nützliche Begleiter: gcc -dM -E - < /dev/null listet jedes vom Compiler vordefinierte Makro auf, und gcc -E -P datei.c lässt das Rauschen der Zeilenmarkierungen weg.

Warum Fehler auf die falsche Zeile zeigen

Weil der Compiler expandierten Text sieht, wird ein Fehler in einem Makro dort gemeldet, wo das Makro benutzt wurde, nicht dort, wo es geschrieben wurde:

#define HALF(x) (x / 2

int main(void) {
    int y = HALF(8);   /* der Fehler wird hier gemeldet */
    return 0;
}

Die fehlende Klammer steht in der #define-Zeile, aber der Compiler beschwert sich über die Zeile mit HALF(8), oft mit einer Meldung über ein unerwartetes Token, das im Zusammenhang keinen Sinn ergibt. Wenn ein Fehler unmöglich aussieht, expandiere die Datei mit gcc -E und lies die echte Zeile.

Moderne Compiler helfen: GCC und clang geben einen Hinweis „in expansion of macro" aus, der zurück auf die Definition zeigt. Kompiliere mit -Wall -Wextra, damit du diese Hinweise tatsächlich siehst.

Vordefinierte Makros

Der Präprozessor liefert selbst einige Makros mit. Diese sind für Diagnosen wirklich nützlich:

__FILE__ und __LINE__ expandieren zum aktuellen Dateinamen und zur aktuellen Zeilennummer, und so melden Zusicherungs- und Protokollmakros, wo etwas schiefging. (__func__ ist ein wenig anders - es ist ein echter Bezeichner, den der Compiler bereitstellt, kein Präprozessormakro, aber es wird genauso benutzt.)

Compiler definieren auch Plattformmakros wie __linux__, _WIN32 und __APPLE__ vor. Code, der sich je System unterscheiden muss, prüft diese mit #ifdef, was das Thema der bedingten Übersetzung ist.

Was man mitnehmen sollte

Der Präprozessor ist klein und er ist dumm, und beides ist Absicht. Er gibt dir drei Fähigkeiten - eine Datei hereinholen, Text ersetzen, Code an- und ausschalten - und keinerlei Typprüfung dazu.

Dieser Handel ist der Grund, warum der Standardrat lautet, zuerst zu Sprachmitteln zu greifen: Nimm für Konstanten const int oder ein enum statt #define, wenn du kannst, und eine echte Funktion statt eines funktionsartigen Makros. Wo der Präprozessor wirklich das richtige Werkzeug ist - Header, Portabilitätsschalter, Konfiguration zur Übersetzungszeit -, ist er unersetzlich.

Als Nächstes nimmt Makros #define ernst: Argumente, die Klammerregeln und die Fallen, die entstehen, wenn man Text in Code einsetzt, den man nicht geschrieben hat.

Häufig gestellte Fragen

Was ist der Präprozessor in C?

Ein Textverarbeitungsschritt, der vor der Übersetzung läuft. Er befolgt die Zeilen, die mit # beginnen - fügt den Inhalt von Header-Dateien anstelle von #include ein, ersetzt mit #define definierte Makros und löscht oder behält Code gemäß #if/#ifdef. Der Compiler sieht immer nur das Ergebnis, nie deine ursprüngliche Datei.

Was ist der Unterschied zwischen #include <stdio.h> und #include "meinedatei.h"?

Spitze Klammern durchsuchen die System-Include-Verzeichnisse des Compilers, wo die Header der Standardbibliothek liegen. Anführungszeichen durchsuchen zuerst das Verzeichnis der aktuellen Datei und weichen dann auf die Systempfade aus. Nimm spitze Klammern für Bibliotheksheader und Anführungszeichen für selbstgeschriebene Header.

Wie kann ich sehen, was der Präprozessor erzeugt hat?

Führe gcc -E datei.c aus, um nach dem Vorverarbeiten anzuhalten und den expandierten Quelltext auszugeben. Bei einer Datei, die <stdio.h> einbindet, sind das Tausende Zeilen, leite es also weiter: gcc -E datei.c | tail -30 zeigt nur deinen eigenen Code mit allen bereits ersetzten Makros.

Ist #include eine C-Anweisung?

Nein. Direktiven gehören nicht zur eigentlichen Sprache C - sie haben ihre eigene zeilenbasierte Syntax, bekommen kein Semikolon und sind verschwunden, wenn der Compiler dein Programm liest. Deshalb zeigt sich ein Fehler in einem Makro als verwirrende Meldung in einer Zeile, die in Ordnung aussieht.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S