Programmiersprache C/C++

Präprozessor

Direktive #define

  #define bezeichner ersatzliste 

  #define bezeichner(bezeichner_liste) ersatzliste
Im ersten Fall wird eine symbolische Konstante, im zweiten Fall ein Makro definiert.

Die Direktive #define ersetzt jedes weitere Vorkommen von bezeichner im Quelltext durch die als ersatzliste angegebene Zeichenfolge.
Dieser Ersatz betrifft nur Terminalsymbole (also eigenständige Vorkommen von bezeichner im Quelltext), nicht aber Zeichenfolgen, die in einem Kommentar erscheinen, Teil eines anderen Bezeichners oder Teil einer Stringkonstanten sind.
ersatzliste besteht aus einer Folge von Symbolen, es können reservierte Wörter, Konstanten und vollständige Anweisungen enthalten sein.
ersatzliste muß durch ein oder mehrere "weiße" Leerzeichen (whitespaces) von bezeichner getrennt sein. Leerzeichen, die ersatzliste vorangestellt wurden, sind ebenso wie nachfolgende Leerzeichen Teil des Ersatztexts.
Wenn hinter bezeichner eine bezeichner_liste erscheint, ersetzt die Direktive #define jedes Vorkommen von bezeichner(bezeichner_liste) mit einer Version von ersatzliste, bei der die dort angegebenen Parameter den aktuellen Variablennamen angepaßt werden:
Die Bezeichnerliste stellt in diesem Fall eine Parameterliste für den Makro dar.

Wenn ein Makro mit Parametern existiert, werden weitere Vorkommen des verwendeten Bezeichners zusammen mit einer entsprechenden Bezeichnerliste als Makroaufruf interpretiert, der zu einer entsprechenden Erweiterung (also dem Einsetzen der Ersatzliste in den Quelltext) führt. Der Präprozessor paßt bei derartigen Erweiterungen die in der Ersatzliste verwendeten Bezeichner an: Jeder Parameter aus dieser Liste, dem kein String- (#), Zeichen- (#@) oder Symboloperator (##) vorausgeht und kein Symboloperator folgt, wird durch den im Makroaufruf verwendeten Variablennamen ersetzt. Sollte der Makroaufruf seinerseits Makrobezeichner als Parameter verwenden, dann werden diese Makros vor dem Einsetzen in die Ersatzliste erweitert.
Parameternamen in der Ersatzliste dienen als Platzhalter für die bei einer Erweiterung des Makros einzusetzenden aktuellen Bezeichner und werden ohne Einschränkungen durch den Kontext ersetzt: Ein Parametername kann bei Bedarf mehrfach in der Ersatzliste erscheinen, eine bestimmte Reihenfolge ist dabei nur durch die Logik des Makros selbst gefordert.

Da Makros im Gegensatz zu Funktionen nicht mit variabler Parameteranzahl arbeiten können, muß ein Makroaufruf grundsätzlich so viele aktuelle Parameter angeben, wie sie die Ersatzliste enthält.
Speziell bei komplexeren Parametern sollte mit entsprechender Klammerung dafür gesorgt werden, daß keine Mißverständnisse in Bezug auf die Auswertung und Zusammengehörigkeit der einzelnen Ausdrücke entstehen.

Rekursive Erweiterungen finden bei Parameternamen nicht statt:
Wenn die Definition eines Makros xyz den Bezeichner xyz in der Ersatzliste enthält, wird dieser Bezeichner nicht erneut erweitert - auch dann nicht, wenn sich die Zeichenfolge xyz erst durch die Erweiterung eines anderen Makros ergibt.

Die Parameter in der Bezeichnerliste einer Makrodefinition müssen durch Kommas voneinander getrennt werden und innerhalb dieser Liste jeweils eindeutig sein.
Beispiel:

  #define MIN(x,y)  ((x<y) ? x : y)
Der Geltungsbereich der bei Makrodefinitionen verwendeten Bezeichner erstreckt sich ausschließlich auf die jeweilige Definition, d.h. er endet mit der Ersatzliste.

Zwischen dem Bezeichner des Makros und der öffnenden Klammer darf kein Leerzeichen stehen.
Beispiel:

  #define SQR(a)  ( a * a)      /* korrekt */
  #define SQR (a) ( a * a)      /* fuehrt zu Syntaxfehler */
Makrodefinitionen können sich über mehrere Zeilen erstrecken. Die Zusammengehörigkeit der Zeilen müß durch einen Rückstrich \ (backslash) am Ende einer fortzusetzende Zeile angezeigt werden.
Beispiel:
  #define LONGTEXT "Dies ist ein langer Text, der auf " \
                   "der naechsten Zeile fortgesetzt wird."

Beispiel:

  #define BREITE       80           
  #define LAENGE     ( BREITE + 10 )

  int var;

  var = LAENGE * 20;   /* liefert var = ( 80 + 10 ) * 20;  */
Der Präprozessor operiert nur mit Zeichenketten, d.h. aus
  LAENGE * 20
                                wird zunächst 
  ( BREITE + 10 ) * 20
                                und dann 
  ( 80 + 10 ) * 20
                                jedoch nicht 180 !
Fehlerquelle:

Klammern können bei der Notation von Ausdrücken sehr wichtig sein !

  #define BREITE       80           
  #define LAENGE       BREITE + 10 

  int var;

  var = LAENGE * 20;   /* liefert var = 80 + 10 * 20;  */
Während zuvor für var 180 als Wert berechnet wurde, ergibt sich jetzt 280 als Wert !

Da Parameternamen mehrfach in der Ersatzliste von Makros erscheinen können und der Präprozessor bei Erweiterungen jeweils den gesamten Ausdruck einsetzt, können Parameter mit Seiteneffekten unter Umständen zu unerwarteten Ergebnissen führen.


Beispiel:

  #define NMAX 100

  var=NMAX;            /* liefert var=100;  */
Fehlerquelle:
  #define NMAX = 100

  var=NMAX;            /* liefert var== 100;  */

Makros können im Prinzip wie Funktionen benutzt werden, jedoch ist die Arbeitsweise nicht identisch !
Das folgende Beispiel zeigt einen gefährlichen Seiteneffekt:

  #define SQUARE(a) ( a * a)   /* liefert das Quadrat */
  
  int square(int a)
  {
    return ( a*a );
  }

  int main(void)
  {
    int a1 = 1, a2 = 1;
    int b, c;
  
    b = SQUARE(++a1);        /* liefert c = ( ++a1 * ++a1 );  */
    c = square(++a2);

    return 0;
  }
a1 wird nicht wie a2 einmal, sondern zweimal inkrementiert. Während c bei der Ausführung des Programms den korrekten Wert 4 erhät, wird an b der Wert 6 zugewiesen.

Der Präprozessor führt folgende Ersetzung durch:

  c = ++a * ++a;
Konsequent wird a nicht einmal, sondern zweimal erhöht: c bekommt anstelle von 4 den Wert 6 zugewiesen.


Definieren einer Kennung, die anzeigt, daß eine bestimmte Header-Datei bereits eingefügt wurde (Vermeidung von Re-Definitionen).
Beispiel: Ausschnitt aus der Header-Datei stdio.h

  #ifndef _STDIO_H
  #define _STDIO_H
  ...
  #endif
Diese Technik wird in allen Header-Dateien des Laufzeitsystems eingesetzt.


Ein #define ohne Angabe einer Ersatzliste entfernt nachfolgende Vorkommen des jeweiligen Bezeichners komplett aus dem Quelltext, d.h. ersetzt sie durch ein einzelnes Leerzeichen. Der Bezeichner gilt auch in diesem Fall weiterhin als definiert: Prüfungen mit #if defined ergeben TRUE, auf erneute (nicht identische) Redefinitionen des Bezeichners reagiert der Compiler mit einer Fehlermeldung.

Beispiel:
Verstehen ältere Compiler das Schlüsselwort volatile nicht, so kann durch

  #define volatile
erreicht werden, daßer der Compiler einen Quelltext erhält, der dieses Schlüsselwort nicht mehr enthält.


In C++ machen Inline-Funktionen und Deklarationen mit const die Direktive #define größtenteils überflüssig. Weitere Informationen über Makros und das Ersetzen von Text finden Sie unter den Stichworten Stringoperator, Zeichenoperator und Symboloperator.


Die Anwendung von Makros zur Neudefinition von C-Standardfunktionen birgt ein gewisses Risiko in sich.
Beispiel:

  #include <stdio.h>

  #define sqrt(x)  ((x)<0 ? sqrt(-x) : sqrt(x))

  int main(void)
  {
    double x = 1.0, y = -1.0;

    printf("x = %lf  sqrt(x) = %lf\n", x, sqrt(x));
    printf("y = %lf  sqrt(y) = %lf\n", y, sqrt(y));

    return 0;
  }
Testergebnisse:
  gcc, unter Solaris 2.3, AIX 3.2 und DOS
    Warnung: type mismatch in implicit declaration for 
    built-in function sqrt
    Korrektes Ergebnis
  lcc, unter Solaris 2.3
    Korrektes Ergebnis
  cc, auf Motorola
    Korrektes Ergebnis
  xlc, unter AIX 3.2
    Fehlerhaftes Ergebnis
  MSC, unter DOS
    Fehlerhaftes Ergebnis

Rekursive Makrodefinitionen sind in ANSI-C möglich.
Beispiel:

  #define  char  unsigned char
Ältere Compiler können bei rekursiven Makrodefinitionen in Schwierigkeiten kommen.


Einer symbolischen Konstanten (einem Makro) kann erst beim Compilieren ein Wert zugewiesen werden.

Beispiel:

  #include <stdio.h>
  
  int main(void)
  {
    printf("N = %d\n", N);
   
    return 0;
  }
Die Übersetzung des obigen Programms (Dateiname def.c) mittels des Befehls
  cc -o def def.c
führt zu einer Fehlermeldung folgender Art: Undeclared identifier N.
Es ist jedoch möglich, beim Compiler-Aufruf eine symbolische Konstante N zu definieren:
  cc -o def def.c -DN=6
Das Programm kann jetzt übersetzt werden und liefert
  N = 6
Das obige Beispielprogramm sollte jedoch besser wie folgt formuliert werden:
  #include <stdio.h>
  
  int main(void)
  {
   #ifdef N
    printf("N = %d\n", N);
   #else
    #error Symbolische Konstante N nicht definiert
   #endif

    return 0;
  }
Durch die #error-Direktive wird ein fehlerhafter Übersetzungslauf verhindert, falls N nicht definiert ist.


Einige Makros aus stdio.h

  #define getchar()   getc(stdin)
  #define putchar(x)  putc(x,stdout)
  #define fgetpos(stream, pos) (((*(pos) = ftell(stream)) == -1) ? -1 : 0)
  #define fsetpos(stream, pos) (fseek((stream), *(pos), SEEK_SET))

Zurück zum Menü
Zurück zur vorigen Seite Weiter zur nächsten Seite

P. Böhme, 23.03.1996