Movatterモバイル変換


[0]ホーム

URL:


Zum Inhalt springen
WikipediaDie freie Enzyklopädie
Suche

C-Präprozessor

aus Wikipedia, der freien Enzyklopädie

DerC-Präprozessor (cpp, auchCPrecompiler) ist derPräprozessor derProgrammiersprache C. In vielen Implementierungen ist er ein eigenständigesComputerprogramm, das durch denCompiler als erster Schritt der Übersetzung aufgerufen wird. Der Präprozessor bearbeitet Anweisungen zum Einfügen vonQuelltext (#include), zum Ersetzen vonMakros (#define), und für bedingte Übersetzung (#if). Die Sprache der Präprozessor-Anweisungen ist nicht spezifisch zur Grammatik der Sprache C. Deshalb kann der C-Präprozessor auch zur Bearbeitung andererDateitypen verwendet werden.

Hintergrund

[Bearbeiten |Quelltext bearbeiten]

Die Programmiersprache C verfügte in ihren frühesten Versionen über keinen Präprozessor. Er wurde unter anderem auf Betreiben vonAlan Snyder (siehe auch:Portable C Compiler) eingeführt, vor allem aber, um in C das Einfügen anderer Quelltextdateien wie inBCPL (einer Vorgängersprache von C) zu erlauben und das Ersetzen einfacher parameterloser Makros zu ermöglichen. Erweitert vonMike Lesk undJohn Reiser um parameterbehaftete Makros und Konstrukte zur bedingten Übersetzung, entwickelte er sich im Laufe der Zeit vom optionalen Zusatzprogramm eines Compilers zu einer standardisierten Komponente der Programmiersprache. Die von der Kernsprache unabhängige Entwicklung erklärt die Diskrepanzen in der Sprachsyntax zwischen C und dem Präprozessor.[1][2]

In den Anfangsjahren war der Präprozessor ein eigenständiges Programm, das sein Zwischenergebnis an den eigentlichen Compiler übergab, der es dann übersetzte. Heute werden die Präprozessor-Anweisungen von den Compilern für C++ und C in einem einzigen Arbeitsgang mitberücksichtigt. Auf Wunsch kann von ihnen zusätzlich oder ausschließlich das Resultat ausgegeben werden, das ein Präprozessor geliefert hätte.

Der C-Präprozessor als Textersetzer

[Bearbeiten |Quelltext bearbeiten]

Da sich der C-Präprozessor nicht auf die Beschreibung der Sprache C stützt, sondern ausschließlich seine ihm bekannten Anweisungen erkennt und bearbeitet, kann er auch als reiner Textersetzer für andere Zwecke verwendet werden.

Beispielsweise wird für die Kompilierung vonRessource-Dateien auch der C-Präprozessor verwendet. Dieser erlaubt es C-Headerdateien einzubetten und ermöglicht somit Werte, zum Beispiel mit#define definierte Zahlwerte oder Zeichenketten, zwischen C-Code und Ressource-Code zu teilen. Wichtig ist dabei, dass der Ressource-Compiler keinen komplexen C-Code verarbeiten kann. Der C-Code, wie Funktions-Deklarationen oder Struktur-Definitionen, kann dabei mit einem#if oder#ifdef bedingt für den Ressource-Compiler ausgeblendet werden, wobei bestimmte Makros vom Ressource-Compiler definiert werden (RC_INVOKED), die beim C-Compiler nicht definiert sind. Dies nutzt auch die Header-Dateiwindows.h aus, die somit sowohl in Programm-Code wie auch in Ressource-Code (teilweise) genutzt werden kann[3].

Phasen

[Bearbeiten |Quelltext bearbeiten]

Der C-Standard definiert unter anderem die nachfolgenden vier (von insgesamt acht)Übersetzungsphasen. Diese vier werden vom C-Präprozessor durchgeführt:

  1. Ersetzung vonTrigraph-Zeichen durch das korrespondierende einzelne Zeichen.
  2. Zusammenführung von Zeilen, die durch denumgekehrten Schrägstrich (\) am Zeilenende aufgeteilt wurden (gedacht etwa für lange Zeichenketten, beiLochkarten oder Magnetbändern mit fester Record-Länge).
  3. Ersetzung von Makros und Einschleusen von Dateiinhalten: Präprozessor-Anweisungen zum Einschleusen von Dateiinhalten (zusätzlich zu übersetzender Quelltext) und für bedingte Übersetzungen werden ausgeführt. Gleichzeitig werden Makros expandiert.

Einschleusen von Dateiinhalten

[Bearbeiten |Quelltext bearbeiten]

Die häufigste Nutzung des Präprozessors besteht im Einschleusen anderer Dateiinhalte:

#include<stdio.h>intmain(void){printf("Hello, world!\n");return0;}

Der Präprozessor ersetzt die Zeile#include <stdio.h> mit dem Inhalt derHeader-Dateistdio.h, in der unter anderem dieFunktionprintf() deklariert wird. Die Dateistdio.h ist Bestandteil jeder C-Entwicklungsumgebung.

Die#include-Anweisung kann auch mit doppelten Anführungszeichen (#include "stdio.h") verwendet werden. Dann wird bei der Suche nach der betroffenen Datei zusätzlich zu den Verzeichnissen des C-Compilers auch das aktuelle Verzeichnis imDateisystem durchsucht. Durch Optionen für den C-Compiler, der diese wiederum an den C-Präprozessor weiterreicht, oder durch Aufrufoptionen für den C-Präprozessor kann festgelegt werden, in welchen Verzeichnissen nachinclude-Dateien gesucht werden soll.

Eine allgemein übliche Konvention legt fest, dassinclude-Dateien dieDateinamenserweiterung.h erhalten. Originäre C-Quelldateien erhalten die Dateinamenserweiterung.c. Das ist jedoch nicht zwingend vorgeschrieben. Auch Inhalte aus Dateien mit anderer Dateinamenserweiterung als.h können auf diese Art eingeschleust werden.

Innerhalb einzuschleusender Dateien wird häufig durch bedingte Ersetzung dafür gesorgt, dass Deklarationen für die nachfolgenden Compiler-Phasen nicht mehrfach wirksam werden, sofern der Dateiinhalt mehrfach durch#include eingeschleust wird.

Bedingte Ersetzung

[Bearbeiten |Quelltext bearbeiten]

Die Anweisungen#if,#ifdef,#ifndef,#else,#elif und#endif werden für bedingte Ersetzungen des C-Präprozessors verwendet,z. B.

#ifdef WIN32#include<windows.h>#else#include<unistd.h>#endif

In diesem Beispiel prüft der C-Präprozessor, ob ihm ein Makro namensWIN32 bekannt ist. Ist das der Fall, wird der Dateiinhalt von<windows.h> eingeschleust, ansonsten der von<unistd.h>. Das MakroWIN32 kann implizit durch den Übersetzer (z. B. durch alle Windows-32-Bit-Compiler), durch eine Aufrufoption des C-Präprozessors oder durch eine Anweisung mittels#define bekannt gemacht werden.

Im folgenden Beispiel wird der Aufruf vonprintf nur beibehalten, sofern das MakroVERBOSE an dieser Stelle einen numerischen Wert von 2 oder mehr aufweist:

#if VERBOSE >=2printf("Kontrollausgabe\n");#endif

Falls in diesem BeispielVERBOSE nicht definiert ist, expandiert es am Vergleichsoperator zu0.

Definition und Ersetzung von Makros

[Bearbeiten |Quelltext bearbeiten]

In C sind Makros ohne Parameter, mit Parametern und (seit C99) auch mit einer variablen Zahl an Parametern zulässig:

#define <MAKRO_NAME_OHNE_PARAMETER> <Ersatztext>#define <MAKRO_NAME_MIT_PARAMETER>(<Parameterliste>) <Ersatztext>#define <MAKRO_NAME_MIT_VARIABLEN_PARAMETERN>(<optionale feste Parameterliste>, ...) <Ersatztext>

Bei Makros mit Parametern ist zwischen dem Makronamen und der öffnenden runden Klammer kein Leerraum zugelassen. Ansonsten wird das Makro inklusive der Parameterliste als reiner Textersatz für den Makronamen verwendet. Zur Unterscheidung von Funktionen bestehen die Namen von Makros üblicherweise ausschließlich aus Großbuchstaben (guterProgrammierstil). EineEllipse („...“) zeigt an, dass das Makro an dieser Stelle ein oder mehrere Argumente akzeptiert. Auf diese kann im Ersatztext des Makros mit dem speziellen Bezeichner__VA_ARGS__ (VA steht für „variable argument list“) Bezug genommen werden.

Makros ohne Parameter werden beim Auftreten des Makronamens im Quelltext durch ihren Ersatztext (der auch leer sein kann) ersetzt. Bei Makros mit Parametern geschieht das nur, wenn nach dem Makronamen eine Parameterliste folgt, die in runde Klammern eingeschlossen ist und in der Parameteranzahl der Deklaration des Makros entspricht. Beim Ersetzen von Makros mit variabler Parameterzahl werden die variablen Argumente inklusive der sie trennenden Kommata zu einem einzigen Argument zusammengefasst und im Ersatztext statt__VA_ARGS__ eingefügt.

Makros ohne Parameter werden häufig für symbolische Namen von Konstanten verwendet:

#define PI 3.14159

Ein Beispiel für ein Makro mit Parametern ist:

#define CELSIUS_ZU_FAHRENHEIT(t) ((t) * 1.8 + 32)

Das MakroCELSIUS_ZU_FAHRENHEIT beschreibt die Umrechnung einer Temperatur (angegeben als Parametert) aus derCelsius- in dieFahrenheit-Skala. Auch ein Makro mit Parametern wird im Quelltext ersetzt:

intfahrenheit,celsius=10;fahrenheit=CELSIUS_ZU_FAHRENHEIT(celsius+5);

wird durch den C-Präprozessor ersetzt zu:

intfahrenheit,celsius=10;fahrenheit=((celsius+5)*1.8+32);

Makros mit einer variablen Anzahl von Parametern bieten sich an, um Argumente an einevariadische Funktion zu übergeben:

#define MELDUNG(...) fprintf(stderr, __VA_ARGS__)

Zum Beispiel wird:

inti=6,j=9;MELDUNG("DEBUG: i = %d, j = %d\n",i,j);

durch den C-Präprozessor ersetzt zu:

inti=6,j=9;fprintf(stderr,"DEBUG: i = %d, j = %d\n",i,j);

Da in C aufeinanderfolgendeZeichenkettenliterale während der Übersetzung zusammengefasst werden, ergibt sich hieraus ein gültiger Aufruf der Bibliotheksfunktionfprintf.

In älteren, C90-kompatiblen Quelltexten findent man dafür den folgenden Workaround:

#define MELDUNG(x) printf xMELDUNG(("DEBUG: i = %d, j = %d\n",i,j));

Das heißt, der Makroaufrufmuss mit doppelten runden Klammern erfolgen, um so schließlichein Argument zu übergeben. Das Makro wiederummuss eine Funktion aufrufen, die genau die übergebene Parameterliste erwartet. Notfalls muss man dafür eine passende (Ausgabe-)Funktion schreiben, da der Präprozessor keine Stringbearbeitung kennt, um die Parameterliste zu ändern oder zu erweitern.

Makro über mehrere Zeilen

[Bearbeiten |Quelltext bearbeiten]

Da in der zweiten Phase des C-Präprozessors durch das Zeichen\ am Zeilenende schon die Zusammenführung auf eine Zeile erfolgt, können Makros durch diesen Mechanismus auf mehreren Zeilen deklariert werden.

Makrodefinition zurücknehmen

[Bearbeiten |Quelltext bearbeiten]

Eine vorherige Makrodefinition kann mit#undef wieder rückgängig gemacht werden. Das dient dazu, Makros nur in einem begrenzten Codeabschnitt verfügbar zu machen:

#undef CELSIUS_ZU_FAHRENHEIT/* Der Geltungsbereich des Makros endet hier */

Umwandlung eines Makroparameters in eine Zeichenkette

[Bearbeiten |Quelltext bearbeiten]

Wird einem Parameter im Ersatztext eines Makros ein# vorangestellt, so wird bei der Ersetzung das Argument durch Einschließen in doppelte Hochkommata in eine Zeichenkette umgewandelt(stringized). Folgendes Programm gibtstring aus, nichthallo:

#include<stdio.h>#define STR(X) #Xintmain(void){charstring[]="hallo";puts(STR(string));return0;}

Verkettung von Makroparametern

[Bearbeiten |Quelltext bearbeiten]

Der Verkettungsoperator## erlaubt es, zwei Makroparameter zu einem zu verschmelzen(englisch: token pasting). Das folgende Beispielprogramm gibt die Zahl234 aus:

#include<stdio.h>#define GLUE(X, Y) X ## Yintmain(void){printf("%d\n",GLUE(2,34));return0;}

Die Operatoren# und## ermöglichen bei geschickter Kombination das halbautomatische Erstellen beziehungsweise Umstellen ganzer Programmteile durch den Präprozessor während der Übersetzung des Programms, was allerdings auch zu schwer durchschaubarem Code führen kann.[4]

Standardisierte Makros

[Bearbeiten |Quelltext bearbeiten]

Zwei vordefinierte Makros sind__FILE__ (aktueller Dateiname) und__LINE__ (aktuelle Zeile innerhalb der Datei):

#include<stdio.h>#include<stdlib.h>#define MELDUNG(text) fprintf(stderr, \    "Datei [%s], Zeile %d: %s\n", \    __FILE__, __LINE__, text)intmain(void){MELDUNG("Kapitaler Fehler. Programmende.");returnEXIT_FAILURE;}

Im Fehlerfall wird so vor dem Programmende folgender Text ausgegeben:

Datei [beispiel.c], Zeile 10: Kapitaler Fehler. Programmende.

Gefahren von Makros

[Bearbeiten |Quelltext bearbeiten]
  • Wichtig ist, dass bei der Deklaration von Makros mit Berechnungenjeder Parameter geklammert werden muss, damit beim Aufruf des Makros immer das gewünschte Ergebnis erreicht wird. Wäre im Beispiel der Temperaturumrechnung die Klammerung um den Parametert im Ersatztext nicht erfolgt, so wäre als Ersetzung das (mathematisch falsche und nicht gewünschte) Ergebnis(celsius + 5 * 1.8 + 32) entstanden.
  • Bei Makroaufrufen sind Argumente mit den Operatoren++ und-- sowie Funktionen und Zuweisungen als Argumente zu vermeiden, da diese durch eventuelle Mehrfachauswertung zu unerwünschtenSeiteneffekten oder sogar undefiniertem Code führen können.
  • Die Verwendung vonSemikolon im Ersatztext als Ende einer C-Anweisung oder als Trenner zwischen mehreren im Makroersatz angegebenen C-Anweisungen sollte vermieden werden, da dies Nebeneffekte auf den weiter zu übersetzenden Quelltext bewirken kann.
  • Makros sind global und lassen sich nicht in Namensräume (C++-Konstrukt) einsperren. Einmal vergebene Bezeichner dürfen weder alslokale Variablen noch alsStrukturmember auftauchen, da hier der C-typischeVerdeckungseffekt nicht greift. Daher müssen diesefür das gesamte Projekt eineindeutig sein, was bei der Namensvergabe zu beachten ist.

Weniger fehleranfällige Programmierung, insbesondere in C++, vermeidet Makros wo immer das möglich ist. Beispielsweise lassen sich symbolische Integer-Konstanten in C und C++ mittelsenum festlegen, Gleitkomma-Konstanten mitconstexpr (C++ ab 2011), bedingte Compilierung mitif (0) { }, und kleine Berechnungen mit Inline-Funktionen, ggf. mit Templates.

Gezielter Abbruch der Übersetzung

[Bearbeiten |Quelltext bearbeiten]

Mit der Anweisung#error kann der Übersetzungsvorgang abgebrochen und eine Meldung ausgegeben werden:

#include<limits.h>#if CHAR_BIT != 8#error "Dieses Programm unterstützt nur Plattformen mit 8bit-Bytes!"#endif

Ändern des Dateinamens und der Zeilennummern

[Bearbeiten |Quelltext bearbeiten]

Mittels der Anweisung#line ist es möglich, aus Sicht des Compilers die Nummer der darauf folgenden Zeile und auch den für Meldungen verwendeten Namen der aktuellen Quelldatei zu manipulieren. Dies hat Auswirkungen auf etwaige nachfolgende Compilermeldungen:

#line 42/* Diese Zeile hätte in einer Compilermeldung jetzt die Nummer 42. */#line 58 "scan.l"/* In einer Meldung wäre dies Zeile 58 der Datei ''scan.l'' */

Genutzt wird dieser Mechanismus oft von Codegeneratoren wie beispielsweiselex oderyacc, um im erzeugten C-Code auf die entsprechende Stelle der Ursprungsdatei zu verweisen. Dadurch wird die Fehlersuche stark vereinfacht.

Beeinflussung des Compilers

[Bearbeiten |Quelltext bearbeiten]

Die Präprozessoranweisung#pragma erlaubt es, den Compiler zu beeinflussen. Derartige Kommandos sind meist compilerspezifisch, einige definiert aber auch der C-Standard (ab C99), z. B.:

#include<fenv.h>#pragma STDC FENV_ACCESS ON/* Im Folgenden muss der Compiler davon ausgehen, dass das Programm Zugriff aufStatus- oder Modusregister der Fließkommaeinheit nimmt. */

Erkennung des verwendeten Compilers und dessen Version

[Bearbeiten |Quelltext bearbeiten]

Die Programmiersprache C bietet selbst keine Möglichkeit den für ein Programm verwendeten Compiler und dessen Version zur Laufzeit des Programms auszugeben. Hierfür haben die meisten C Compilerhersteller Makros definiert[5], die sich mit dem C-Präprozessor auswerten lassen.

intmain(){...#if defined ( __clang__ )printf("Clang %i.%i.%i\n",__clang_major__,__clang_minor__,__clang_patchlevel__);#elif defined( __GNUC__ )printf("GCC %i.%i.%i\n",__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__);#elif defined ( _MSC_VER )printf("Microsoft Visual C++ %i\n",_MSC_VER);#elseprintf("Unbekannter C-Compiler\n");#endif...}

Literatur

[Bearbeiten |Quelltext bearbeiten]

Einzelnachweise

[Bearbeiten |Quelltext bearbeiten]
  1. Dennis M. Ritchie: The Development of the C Language. Abgerufen am 12. September 2010 (englisch). 
  2. Rationale for International Standard – Programming Languages – C. (PDF; 898 kB) S. 15 (Abschnitt 5.1.1.2), abgerufen am 12. September 2010 (englisch). 
  3. msdn.microsoft.com
  4. The C Preprocessor – Concatenation. Abgerufen am 25. Juli 2014 (englisch). 
  5. https://sourceforge.net/p/predef/wiki/Compilers/ Pre-defined Compiler Macros Wiki
Abgerufen von „https://de.wikipedia.org/w/index.php?title=C-Präprozessor&oldid=260469858
Kategorie:

[8]ページ先頭

©2009-2026 Movatter.jp