FAQ:Der C Präprozessor

[ FAQ in de.comp.lang.c ]


Diese FAQ bezieht sich in ihrer Gänze auf den inzwischen nicht mehr aktuellen ISO-C Standard 9899:1990, vielfach auch als C90 bezeichnet. Der seit Dezember 1999 existierende neue ISO 9899:1999 Standard (oder auch C99) wird nicht berücksichtigt.


[ Inhalt ][ Index ][ ZurÜck ][ Weiter ]


Frage 6.1: Wie schreibe ich ein Makro, um zwei Werte zu vertauschen?

Antwort: Darauf gibt es keine wirklich gute Antwort. Falls beides Integer-Werte sind, kann eventuell der bekannte Trick mit mehreren XORs verwendet werden. Das funktioniert aber nicht für Gleitkomma- oder Zeiger-Typen. Schlimmer noch, es funktioniert auch nicht, wenn die beiden Werte die gleiche Variable sind (und die "offensichtliche" hyper-komprimierte Implementation für integrale Typen a^=b^=a^=b ist illegal weil sie mehrfache Nebeneffekte hat, siehe Fragen 4.1 und 4.2). Falls das Makro mit x-beliebigen Typen funktionieren soll (was ja meistens gewünscht ist), kann es auch keine Hilfsvariable verwenden, denn der Typ dieser Variable ist unbekannt und ANSI C kennt keinen 'typeof' operator.

Die beste Lösung ist wohl, den Makro-Ansatz aufzugeben, es sei denn, man ist willens, den Typ der Parameter als dritten Parameter mitzugeben.


Frage 6.2: Ich habe alten Sourcen, in denen mittels

	#define Paste(a, b) a/**/b

Identifier zusammengebastelt werden, aber das funktioniert nicht mehr.

Antwort: Manche frühe C-Präprozessoren entfernten Kommentare komplett, deshalb konnte obiges Konstrukt verwendet werden, neue Tokens zu generieren. In ANSI/ISO C (und auch schon in K&R 1) ist aber festgelegt, dass Kommentare durch "Whitespace" ersetzt werden. Da jedoch eine offensichtliche Notwendigkeit existiert, neue Tokens aus anderen zusammenzusetzen, wurde in ANSI/ISO C der "Token-Pasting"-Operator ## eingeführt. Dieser kann wie folgt verwendet werden:

	#define Paste(a, b) a ## b

Siehe auch Frage 5.4.

References: ANSI Sec. 3.8.3.3; ISO Sec. 6.8.3.3; Rationale Sec. 3.8.3.3; H&S Sec. 3.3.9 p. 52.


Frage 6.3: Wie wird ein Makro mit mehreren Statements am besten geschrieben?

Antwort: Üblicherweise ist es das Ziel, das Makro so zu schreiben, dass es wie eine einzelne Anweisung, die aus einem Funktionsaufruf besteht, verwendet werden kann. Dies bedeutet, das Makro selbst darf kein schliessendes Semikolon am Ende haben - dieses wird beim "Aufruf" gesetzt. Somit kann nicht einfach ein Block (d.h. Anweisungen, die in '{' und '}' eingeklammert sind) verwendet werden, denn sonst würde der Compiler Syntaxfehler melden wenn das Makro als if-Zweig einer if-Anweisung mit else-Teil verwendet wird. Die althergebrachte Lösung ist also

	#define MACRO(arg1, arg2) do {  \
		/* Deklarationen */     \
		stmt1;                  \
		stmt2;                  \
		/* ... */               \
		} while (0)             /* Ohne ';' am Schluss! */

Wenn beim Aufruf dann das Semikolon gesetzt wird, ergibt sich aus dieser Ersetzung eine vollständige do-while-Anweisung. Falls im if-Zweig einer if-Anweisung mit else-Teil kein Strichpunkt gesetzt wird, so ist das Resultat auch korrekt. (Ein optimierender Compiler wird "toten" Code, der vom "while (0)" herrührt, entfernen, Programme wie 'lint' werden möglicherweise Warnungen ausgeben.) Falls alle Anweisungen, die in das Makro sollen, einfache Ausdrücke sind (ohne Deklarationen oder Schleifen), gibt es noch eine zweite Möglichkeit: man schreibt eine einzigen, geklammerten Ausdruck mit dem Komma-Operator. Auf diese Art kann sogar ein Wert "zurückgegeben" werden.

References: H&S Sec. 3.3.2 p. 45; CT&P Sec. 6.3 pp. 82-3.


Frage 6.4: Darf eine Headerdatei weitere Headerdateien einbinden?

Antwort: Das ist natürlich erlaubt. Die eigentliche Frage ist wohl, ob es guter Programmierstil ist. Wie bei allen Stilfragen, gibt es auch hier ungefähr so viele Meinungen wie Programmierer. Viele Leute glauben, verschachtelte #includes sollten besser vermieden werden: der weitherum anerkannte "Indian Hill Style Guide" (siehe Frage 14.3) rät davon ab; es kann zu Fehlern auf Grund mehrfacher Definition von Objekten kommen wenn eine Datei mehrmals eingebunden wird, und die Wartung von Makefiles von Hand wird erschwert. Andererseits ist es mit verschachtelten #includes möglich, Headerfiles modular zu verwenden (d.h. jedes Headerfile bindet ein was es benötigt, anstatt sich darauf zu verlassen, dass der Benutzer des Files die benötigten anderen Headerfiles zuerst einbindet). Definitionen können mit grep (oder entsprechenden Programmen) einfach gefunden werden, ohne zu wissen, in welcher Datei sie nun stehen. Headerfiles können mit dem einfachen Trick

	#ifndef HFILENAME_USED
	#define HFILENAME_USED
	... Inhalt des Headerfiles ...
	#endif

"idempotent" gemacht werden, wonach ein mehrfaches Einbinden kein Problem mehr darstellt (es sollte für jedes Headerfile ein anderer Makroname verwendet wird, z.B. "H" gefolgt vom Dateinamen gefolgt von "_USED"). Programme zur automatischen Erzeugung von Makefiles haben keine Probleme mit verschachtelten Headerfiles. Siehe auch Abschnitt 14.

Anmerkung von TW::
Der "Indian Hill Style Guide" ist in dieser Hinsicht wohl veraltet. Gerade unter dem Gesichtspunkt des "Information Hiding" ist der zweite Ansatz klar besser geeignet, eine Applikation zu strukturieren.

References: Rationale Sec. 4.1.2.


Frage 6.5: Funktioniert der sizeof-Operator in Präprozessor-Direktiven?

Antwort: Nein. Präprozessing findet in einer Phase der Übersetzung statt, zu der noch keine Typinformation verfügbar ist. Statt 'sizeof' können die in <limits.h> vordefinierten Konstanten verwendet werden. Noch besser ist es natürlich, das Programm so zu schreiben, dass es unabhängig von der Grösse bestimmter Typen ist.

References: ANSI Sec. 2.1.1.2, Sec. 3.8.1 footnote 83; ISO Sec. 5.1.1.2, Sec. 6.8.1; H&S Sec. 7.11.1 p. 225.


Frage 6.6: Wie kann ich in einer #if Direktive herausfinden, ob eine Maschine big- oder little-endian ist?

Antwort: Das ist nicht möglich, da der Präprozessor alle Arithmetik als long integer ausführt und keine Adressen kennt. Wird diese Information wirklich gebraucht? Meistens ist es besser, Code zu schreiben, der von so etwas unabhängig ist.

References: ANSI Sec. 3.8.1; ISO Sec. 6.8.1; H&S Sec. 7.11.1 p. 225.


Frage 6.7: Ich möchte (dies oder das, meist kompliziert) mit dem Präprozessor umwandeln, kann aber nicht herausfinden, wie's geht.

Antwort: Der C-Präprozessor ist kein Allround-Werkzeug (es ist nicht einmal garantiert, dass er überhaupt ein separates Programm ist.) Statt den Präprozessor zu "vergewaltigen" ist es vielleicht einfacher, ein Programm zu schreiben, das genau das tut, was erwünscht ist. Mit make kann ein solches Programm problemlos in den Entwicklungszyklus eingebaut werden.

Wenn ein Präprozessor für etwas anderes als C Quellen gesucht wird, dann lohnt sich vielleicht ein Blick auf andere Pakete (wie z.B. m4).


Frage 6.8: Ich muß Code warten, der für meinen Geschmack viel zu viele #ifdefs enthält. Wie kann ich diese Sourcen mit dem Präprozessor bearbeiten, so dass nur eine Variante übrig bleibt, ohne dabei auch alle #includes und #defines zu ersetzen?

Antwort: Die Programme "unifdef", "rmifdef" oder "scpp" tun genau das (siehe 17.2).


Frage 6.9: Wie kann ich eine Liste aller vordefinierten Makro-Bezeichner kriegen?

Antwort: Obwohl dies oft benötigt wird, gibt es dafür keine standardisierte Lösung. Wenn die Dokumentation zum Compiler hier nicht weiterhilft, dann gibt es noch die (umständliche) Möglichkeit mit einem Utility wie "strings" das Compiler-Executable zu durchsuchen. Achtung: Viele vordefinierte Macros auf älteren Systemen (z.B. "unix") entsprechen nicht mehr neueren Standards (weil sie den für Benutzerprogramme definierten Namensraum verwenden) und sind deshalb in zukünftigen Versionen des Compilers womöglich nicht mehr verfügbar.


Frage 6.10: Wie kann ich ein Makro mit einer beliebigen Anzahl von Argumenten schreiben?

Antwort: Ein beliebter Trick ist es, ein Makro mit nur einem Argument zu schreiben. Dieses wird dann in Klammern die variable Argumentliste beinhalten:

	#define DEBUG(args) (printf ("DEBUG: "), printf args)

	...
	if (n != 0) DEBUG (("n is %d\n", n));

Der offensichtliche Nachteil dabei ist, dass der Benutzer eines solchen Makros immer daran denken muß, die doppelte Klammerung anzugeben. Andere Möglichkeiten sind die Verwendung von unterschiedlichen Macros mit ähnlichen Namen (DEBUG1, DEBUG2, etc.) die dann eine unterschiedliche Anzahl von Argumenten nehmen, oder Spielereien mit dem Komma:

	#define DEBUG(args) (printf ("DEBUG: "), printf (args))
	#define _           ,

	...
	DEBUG ("i = %d" _ i);

Es ist meistens besser, für solche Zwecke eine Funktion zu verwenden, denn dort gibt es einen wohldefinierten Mechanismus, beliebig viele Argument zu übergeben.

[ Inhalt ][ Index ][ ZurÜck ][ Weiter ]


[ FAQ Logo ]   © 1997-2004 Jochen Schoof (joscho@bigfoot.de)
Diese Version wurde am 14. März 2004 erzeugt. Sie wird zukünftig nicht weiter gepflegt.