12.2. Symbolische Konstanten und Makros

Symbolische Konstanten

Für die Definition von symbolischen Konstanten existiert die insgesamt wohl am meisten verwendete Preprozessor-Anweisung #define . Bei diesem Vorgang wird einer Konstanten beliebigen Datentyps ein symbolischer Name zugewiesen. Das Format für eine solche Anweisung sieht folgendermaßen aus:


#define SYMBOLISCHER_NAME Konstante

Es hat sich eingebürgert, für symbolische Konstanten Großbuchstaben zu verwenden, damit sie auf Anhieb von Variablennamen zu unterscheiden sind. Bei der Wahl des symbolischen Namens gelten die gleichen Einschränkungen wie bei Variablennamen. Er darf vor allem keine Leerzeichen und keine Tabulatoren enthalten, denn der Preprozessor interpretiert alles ab dem ersten "Whitespace"-Zeichen bis zum Zeilenende als Wert für die Konstante. Beispielsweise würde bei


#define SYMBOLISCHER NAME Konstante

jedes Auftreten von SYMBOLISCHER im Quelltext überall durch NAME Konstante ersetzt werden.

Der Compiler selbst bekommt die symbolischen Namen nie zu sehen, weil ja der Preprozessor diese samt und sonders vor der Compilierung durch die entsprechende Konstante ersetzt. Das läßt vermuten, daß symbolische Konstanten nicht dazu dienen, dem Compiler die Arbeit zu erleichtern, sondern den Quelltext für Programmierer besser lesbar zu machen. Dieses Ziel können Sie vor allem dadurch erreichen, indem die symbolischen Namen unmißverständlich Aufschluß über ihren Verwendungszweck geben.

Darüber hinaus hat die Verwendung von symbolischen Konstanten den Vorteil, daß der Wert der Konstanten gegebenenfalls nur an einer Stelle geändert werden muß: Soll z.B. die maximale Länge für eine Eingabezeile von 80 auf 120 Zeichen vergrößert werden, dann müßten normalerweise überall im Quelltext die entsprechenden Anpassungen vorgenommen werden. Eine symbolische Konstante der Art


#define MAX_LINE 80

reduziert dagegen die Arbeit auf die einmalige Änderung der #define-Anweisung.

Schließlich muß noch beachtet werden, daß der Preprozessor keine Ersetzung vornimmt, wenn die symbolische Konstante in Anführungszeichen gesetzt erscheint. Ist beispielsweise HELLO folgendermaßen definiert:


#define HELLO Hello world\n

dann würde die Anweisung printf("HELLO") nicht zur Ausgabe von Hello world\n, sondern von HELLO führen. Damit wie beabsichtigt Hello world\n dargestellt wird, müßten Sie HELLO so definieren:


#define HELLO "Hello world\n"

Hier sind die Anführungszeichen Bestandteil der Konstanten, und printf(HELLO) würde zum gewünschten Resultat führen. Weitere Beispiele für symbolische Konstanten sind:


#define TRUE 1
#define FALSE 0
#define PI 3.14159
#define PROMPT "Befehl> "

Vordefinierte symbolische Konstanten

Der ANSI-C-Standard kennt fünf vordefinierte symbolische Konstanten:

Symbolische Konstante Bedeutung
__FILE__ Name der Quelldatei in Form einer Zeichenkette
__LINE__ Die Zeilennummer der aktuellen Zeile im Quellcode
__DATE__ Das Compile-Datum der Quelldatei im Format "Mmm dd yyyy" (also z.B. "Nov 13 1967")
__TIME__ Die Uhrzeit des Compile-Vorgangs im Format "hh:mm:ss"
__STDC__ steht für die Integer-Konstante 1, falls der verwendete Compiler ANSI-C-konform ist.

Alle fünf symbolischen Konstanten beginnen und enden mit zwei Unterstrichen. Ihre Namen sind reserviert und dürfen nicht in eigenen #define-Anweisungen verwendet werden!

Hier sind einige Beispiele aufgeführt:


printf("Name der Quelldatei: %s\n",
	 __FILE__);

printf("Dieses printf() befindet sich in Zeile %d\n",
	__LINE__);

printf("Compiliert am %s um %s\n",
	__DATE__, __TIME__);

printf("Mein Compiler ist %sANSI-konform!\n",
	__STDC__ == 1? "": "nicht ");

Besonders bei __STDC__ ist zu bedenken, daß ein nicht ANSI-konformer Compiler diese Konstante wahrscheinlich gar nicht kennt. In so einem Fall nimmt der Preprozessor natürlich keine Ersetzung von __STDC__ durch die entsprechende numerische Konstante vor und veranlaßt damit den Compiler zu einer Fehlermeldung. Daher sollten Sie vor der Verwendung von __STDC__ mit der Anweisung #ifdef bzw. #ifndef feststellen, ob diese Konstante überhaupt definiert ist:


#ifndef __STDC__
	#define __STDC__ 0
#endif

Diese Vorgehensweise wird in Abschnitt 12.3 erläutert.

Makros

Während symbolische Konstanten Ersatznamen für beliebige Konstanten darstellen, sind Makros Symbole für eine oder mehrere Anweisungen. Der Gebrauchswert von Makros erhöht sich vor allem durch die Tatsache, daß ihnen Argumente übergeben werden können.

Die Definition von Makros erfolgt in der gleichen Weise wie die von symbolischen Konstanten:


#define SYMBOLISCHER_NAME Anweisungen

Ein Makro ohne Argumente wird genauso behandelt wie eine symbolische Konstante: Der Preprozessor ersetzt bei der Bearbeitung der Quelldatei einfach alle Vorkommnisse des symbolischen Namens durch die entsprechende(n) Anweisung(en). Beispielsweise könnte nach der Definition


#define DRUCKE_HELLO printf("Hello world\n")

DRUCKE_HELLO verwendet werden, um den Text Hello world auszugeben. Interessanter ist dann aber die Variante, bei der Sie den Text in Form eines Arguments an das Makro übergeben können:


#define DRUCKE(x) puts(x) 

In diesem Fall können Sie DRUCKE() genauso verwenden wie puts(), also z.B. DRUCKE("Hello world"). Makros können in gewissen Grenzen sogar Funktionen ersetzen. Sie sind im Vergleich zu diesen schneller, da sie ja vom Preprozessor zu "Inline-Code" expandiert werden, d.h., einfach in eine Serie von Anweisungen innerhalb der aufrufenden Funktion umgewandelt werden. Im Gegensatz dazu ist ein Funktionsaufruf immer mit einem "Overhead" verbunden, da die Argumente erst auf den Stack "gepusht" und die Inhalte bestimmter Prozessor-Register gesichert werden müssen, bevor im Programmablauf verzweigt wird. Beachten Sie folgendes Beispiel:


#include <stdio.h>

#define ISLOWER(x) ((x >= 'a' && x <= 'z') ||\
	x == 'ä' || x == 'ö' || x == 'ü' || x == 'ß')

int main(void){

 char cChar = 'ä';

 /* Das Makro ISLOWER kann wie eine Funktion
  * verwendet werden.
  * Die if Anweisung wird expandiert zu
  * if( ((x >= 'a' && x <= 'z') ||......))
  */
 if( ISLOWER(cChar))
	printf("%c ist ein Kleinbuchstabe\n", cChar);

 return 0;
 }

Bei der Definition von ISLOWER() fällt auf, daß der Zeilenumbruch mit einem '\' maskiert werden muß, weil für den Preprozessor sonst die #define-Anweisung am Ende der ersten Zeile abgeschlossen wäre.

Schon bei einem so einfachen Beispiel macht sich eine große Schwäche von Makros bemerkbar: Im Gegensatz zu Funktionen findet bei ihnen keine Typenüberprüfung statt, d.h., es hindert Sie niemand daran, ISLOWER() mit einem float-Argument aufzurufen. In diesem Beispiel käme dabei wohl nur harmloser Unsinn heraus, bei arithmetischen Berechnungen kann dies aber zu schwer auffindbaren Fehlern führen:


#include <stdio.h>

/* POW(x,y) berechnet x hoch y */
#define POW(x,y){      \
	int iTemp = x;     \
                        \
/* gefährlich ! */     \
	while( y-- > 1 )   \
		x *= iTemp;  \
	}

int main(void){

int iBase = 5;
int iExp = 3;
int *piExp = &iExp;

 printf("%d hoch %d ist", iBase, iExp );

 POW(iBase, iExp )

 printf(" %d\n", iBase );

 return 0;
 }

Dieses Makro scheint eine kurze und schnelle Lösung darzustellen, um x hoch y zu berechnen. Es weist aber zumindest zwei Probleme auf: Wegen der fehlenden Typenüberprüfung bei Makros könnten an POW() auch Fließkommazahlen übergeben werden; allerdings ist die Variable iTemp, der der Anfangswert von x zugewiesen wird, vom Typ int. Das bedeutet, daß gleich bei der automatischen Typenumwandlung ein Informationverlust auftreten würde. Das Ergebnis von POW() wäre im Falle von Fließkommazahlen absolut unbrauchbar!

Noch schwerer wiegt, daß immer von einfachen Variablen als Argumenten ausgegangen wird; es sind keinerlei Vorkehrungen für den Fall getroffen, daß Ausdrücke oder Pointer an POW() übergeben werden. Würde POW() z.B. nicht in der Form POW( iBase, iExp), sondern mit dem Pointer piExp aufgerufen, wäre das Resultat völlig unbrauchbar. Der Aufruf


 POW(iBase, *piExp )

sollte zwar das gleiche Ergebnis haben wie der Aufruf POW(iBase, iExp ), die Anweisung while( y-- > 1 ) wird aber zu


while( *piExp-- > 1 )

expandiert (Die Auswertung dieses Ausdrucks wird in Abschnitt 8.2.2 erläutert)!

Das zeigt, daß alle Parameter-Variablen im Verlauf der ganzen Makro-Definition immer in Klammern zu setzen sind! Wäre nämlich in unserem Beispiel die Schleifenanweisung while( (y)-- > 1 ), dann müßten Sie auch beim Aufruf mit dem Pointer-Argument nichts befürchten.

Übrigens: Viele der scheinbaren Bibliotheksfunktionen sind in Wirklichkeit Makros! Beispielsweise wird getchar() häufig als fgetc(stdin) definiert.

Symbolische Konstanten und Makros löschen

Im Gegensatz zu Variablen, die immer nur einen bestimmten Geltungsbereich haben, stehen symbolische Konstanten ab dem Zeitpunkt ihrer Definition überall in der Quelldatei zur Verfügung; sie können auch nicht wie globale Variablen durch die Definition gleichlautender lokaler Variablen überdeckt werden. Soll eine symbolische Konstante oder ein Makro ab einer bestimmten Stelle im Quelltext nicht mehr gültig sein, dann reicht es, diese mit der Anweisung #undef zu löschen. Der Compiler beschwert sich dann über ungültige Bezeichner, sobald Sie die betreffenden symbolischen Namen nach dem #undef einfach weiterverwenden.

In der Regel verteilen Sie #define- und #undef-Anweisungen allerdings nicht über den ganzen Quelltext, sondern setzen diese Preprozessor-Anweisungen an den Anfang einer Datei. Dort dient #undef vor allem dazu, symbolische Konstanten zu löschen, die gegebenenfalls mit einer include-Datei importiert wurden. Dies ist vor allem dann notwendig, wenn Sie einen bestimmten symbolischen Namen neu vergeben wollen: Eine Neudefinition ist nämlich nur nach dem Löschen der alten Definition möglich.


#define SIZE 127
#undef SIZE
#define SIZE 256

Dieses Fragment definiert die symbolische Konstante SIZE, löscht sie anschließend und erstellt sie neu für die Integer-Konstante 256. Ohne die #undef-Anweisung führt die Neudefinition von SIZE zu einer Fehlermeldung. Falls Sie übrigens versehentlich eine symbolische Konstante löschen wollen, die gar nicht definiert wurde, dann ist das nicht weiter tragisch: Dieses Vorgehen führt zu keiner Fehlermeldung.