18.4 Variadic Makros – __VA_ARGS__ 

Ab dem C99-Standard haben Sie die Möglichkeit, ein Makro mit variablen Argumenten zu definieren und aufzurufen. Zugegeben, dieser Abschnitt hätte auch gut zu Kapitel 10, »Präprozessor-Direktiven«, gepasst, aber ich habe mich dann doch entschieden, dieses Thema hier aufzunehmen.
Das Prinzip ist denkbar einfach. Beim Aufruf eines Makros fasst der Präprozessor alle optionalen Argumente zu einem Argument zusammen. Hierzu wurde der Bezeichner __VA_ARGS__ eingeführt, der im Ersatztext die zusammengefassten optionalen Argumente enthält. Allerdings darf der Bezeichner __VA_ARGS__ nur im Ersatztext der Makrodefinition verwendet werden. __VA_ARGS__ kann dort wie fast ein gewöhnlicher Parameter verwendet werden. Der Unterschied ist, dass der Parameter durch sämtliche Argumente ersetzt wird und nicht nur durch ein Argument.
Hier folgt ein einfaches Beispiel dafür, wie Sie ein Makro erstellen können, das eine variable Anzahl von Argumenten verwendet:
/* variadic1.c */ #include <stdio.h> #include <stdlib.h> #define errprintf(...) fprintf(stderr, __VA_ARGS__) int main(void) { const char str[] = "ein Argument"; int val = 10; errprintf("Hallo Welt %d %s\n", val, str); errprintf("Fehler!! Zeile: %d (%s)\n", __LINE__, __DATE__ ); return EXIT_SUCCESS; }
Das Prinzip ist recht einfach. Der Makroaufruf
errprintf("Hallo Welt %d %s\n", val, str);
sieht nach dem Ersetzen des Textes in __VA_ARGS__ wie folgt aus:
fprintf(stderr, "Hallo Welt %d %s\n", val, str);
Anstatt die Ausgabe auf den Standard-Stream stderr auszugeben, können Sie selbstverständlich auch eine Datei dafür verwenden – vorausgesetzt natürlich, Sie haben eine entsprechende Datei mit FILE-Zeiger zum Schreiben geöffnet. Dies ist ein eleganter und einfacher Weg, um ein Programm mitzuprotokollieren, indem bestimmte Dinge in einer Logdatei geschrieben werden.
Hierzu ein weiteres Beispiel, das noch einige Möglichkeiten demonstrieren soll, ein Makro mit variablen Argumenten zu nutzen:
/* variadic2.c */ #include <stdio.h> #include <stdlib.h> #define fprintf_log(...) fprintf(fp, __VA_ARGS__) #define errprintf(...) fprintf(stderr, __VA_ARGS__) #define checkerror(x, ...) if(!(x)) { \ fprintf(stderr, __VA_ARGS__); } #define LOGFILE "logfile.txt" static FILE *fp; void openLog(void) { fp = fopen(LOGFILE, "w+"); if( NULL == fp ) { errprintf("%s:%d: Konnte Logdatei nicht oeffnen\n", __func__, __LINE__); exit(EXIT_FAILURE); } } int main(void) { char name[80]; int val, check; openLog(); fprintf_log("(%s/%s): Programmstart\n", __DATE__, __TIME__); printf("Bitte Namen eingeben: "); fgets( name, 80, stdin); // Die Eingabe wird mitprotokolliert. fprintf_log("Eingabe \"name\": %s", name); printf("Bitte eine Ganzzahl eingeben: "); check = scanf("%d", &val); // Wird nur ausgeführt, wenn die // Eingabe bei scanf() falsch war. checkerror(check, "Die Eingabe war falsch\n"); return EXIT_SUCCESS; }
Mit dem ersten Makro
#define fprintf_log(...) fprintf(fp, __VA_ARGS__)
können Sie eine variable Anzahl von Argumenten in den Stream fp schreiben – vorausgesetzt natürlich, der Stream fp wurde vorher zum Schreiben geöffnet, was in diesem Beispiel in der Funktion openLog() realisiert wurde. Mit fprintf_log() können Sie somit alles im Programm mitprotokollieren, was Ihnen sinnvoll erscheint.
Ebenfalls recht praktisch ist das folgende Makro:
#define checkerror(x, ...) if(!(x)) { \ fprintf(stderr, __VA_ARGS__); }
Hier wurde noch ein zusätzlicher Parameter verwendet, womit anschließend mit if überprüft wird, ob das Makro überhaupt ausgeführt werden soll. In diesem Fall wird das Makro nur dann ausgeführt, wenn der zusätzliche Parameter x gleich 0 ist. Im Listing wurde dies bei der scanf()-Eingabe verwendet:
int check; check = scanf("%d", &val); checkerror(check, "Die Eingabe war falsch\n");
Wurde bei scanf() eine falsche Eingabe gemacht, beispielsweise ein Zeichen anstatt einer Ganzzahl eingegeben, ist der Wert von check gleich 0. Und beim Aufruf von checkerror() wird dieser scanf()-Rückgabewert als erster Parameter übergeben. Somit würde das Makro checkerror() im Falle eines Fehlers nach der Ersetzung wie folgt aussehen:
if(!(check)) { fprintf(stderr, "Die Eingabe war falsch\n"); }
Im selben Verzeichnis wie das Listing variadic2.c finden Sie außerdem noch die Logdatei logfile.txt, die Sie nach Beendigung des Programms in einem Editor Ihrer Wahl betrachten können.
Hinweis |
Auch hier muss man wieder erwähnen, dass dieses Makro mit variabel langen Argumenten nur mit Compilern funktioniert, die den C99-Standard erfüllen. Hier ist der GNU-GCC unter Linux vorbildlich. Microsoft VC++ beispielsweise kann mit solchen Makros noch nicht umgehen. Es bleibt zu hoffen, dass hier der Standard mit der kommenden 2010er-Version auch komplett implementiert wird. |
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.