8.3 Bedingte Kompilierung
Sie können für den Präprozessor auch Bedingungen formulieren, damit dieser nur dann eine bestimmte symbolische Konstante, ein Makro oder eine Header-Datei verwendet, wenn eine gewisse Bedingung gegeben ist. Folgende Schlüsselwörter stehen Ihnen für die bedingte Kompilierung zu Verfügung (siehe Tabelle 8.1).
Schlüsselwort |
Beschreibung |
---|---|
Wird ausdruck erfüllt (ungleich 0), wird der darauffolgende Programmteil ausgeführt. |
|
Ist symbol im Programm definiert, wird der darauffolgende Programmteil ausgeführt. |
|
Ist symbol im Programmteil nicht definiert, wird der darauffolgende Programmteil ausgeführt. |
|
Trifft ausdruck zu (ungleich 0) oder ist symbol im Programmteil definiert, wird der folgende Programmteil ausgeführt. Einem #elif geht immer ein #if oder ein #ifdef voraus. |
|
Der alternative Programmteil dahinter wird ausgeführt, wenn ein vorausgehendes #if, #ifdef, #ifndef oder #elif nicht ausgeführt wird. |
|
Zeigt das Ende der bedingten Kompilierung an. |
Hierzu ein einfaches Beispiel zur bedingten Kompilierung:
00 // Kapitel8/bedingte_kompilierung.c
01 #include <stdio.h>
02 #include <limits.h>
03 #include <stdlib.h>
04 #define TEST
05 int main(void) {
06 #if UINT_MAX == 4294967295
07 unsigned int val1 = 0;
08 unsigned int val2 = 0;
09 #else
10 unsigned long val1 = 0;
11 unsigned long val2 = 0;
12 #endif
13 val1 = 500 * 1000;
14 val2 = 300 * 200;
15 val1+= val2;
16 printf("%lu\n", (long)val1);
17 #ifdef ULLONG_MAX
18 unsigned long long uval = val1;
19 printf("%llu\n", uval*10000);
20 #else
21 printf("Erweiterte Berechnung nicht moeglich\n");
22 #endif
23 #ifdef TEST
24 printf("UINT_MAX : %u\n", UINT_MAX);
25 printf("ULONG_MAX : %lu\n", ULONG_MAX);
26 #endif
27 #if defined(TEST) && defined(ULLONG_MAX)
28 printf("ULLONG_MAX : %llu\n", ULLONG_MAX);
29 #endif
30 return EXIT_SUCCESS;
31 }
In Zeile (06) prüft der Präprozessor, ob der maximale Wert von UINT_MAX in der Header-Datei limits.h auf dem System gleich 4294967295 ist. Trifft dies zu, wird unsigned int für die Variablen val1 und val2 aus den Zeilen (07) und (08) verwendet. Trifft es hingegen nicht zu, weil das Programm beispielsweise auf einem 16-Bit-System übersetzt wird, wo UINT_MAX dem Wert 65535 entspricht, verzweigt der Präprozessor zur alternativen Verzweigung der Zeile (09) und verwendet in den Zeilen (10) und (11) unsigned long als Datentyp für val1 und val2. Entsprechend dem gewählten Wert werden diese in den Zeilen (13) bis (16) verwendet.
In Zeile (17) wird außerdem geprüft, ob ULLONG_MAX aus der Header-Datei limits.h definiert ist. Dies ist beispielsweise nicht der Fall, wenn der Code auf einem Compiler läuft, der den C99-Standard nicht beherrscht. Wenn ULLONG_MAX definiert ist, wird unsigned long long in den Zeilen (18) und (19) für eine weitere Berechnung verwendet. Der Wert unsigned long wäre dafür nicht mehr geeignet gewesen. Ist ULLONG_MAX hingegen nicht definiert, wird in Zeile (20) zur Alternative verzweigt, wo mit printf() eine Meldung auf dem Bildschirm ausgegeben wird, dass eine erweiterte Berechnung nicht möglich ist.
In Zeile (23) wird dann geprüft, ob eine symbolische Konstante TEST definiert wurde, was in diesem Beispiel in Zeile (04) der Fall ist, weshalb die Testausgabe in den Zeilen (24) und (25) ausgegeben wird. In Zeile (27) wird mit einer UND-Verknüpfung überprüft, ob TEST und ULLONG_MAX in limits.h definiert sind. Trifft beides zu, wird auch hier die Testausgabe der Zeile (28) ausgegeben.
Wollen Sie die Testausgabe nicht mehr ausgeben, müssen Sie entweder Zeile (04) entfernen oder ein #undef vor die erste Verwendung der symbolischen Konstante TEST setzen.
8.3.1 Mehrfaches Inkludieren vermeiden
Sie können mit der bedingten Kompilierung auch überprüfen, ob eine Header-Datei bereits inkludiert wurde, damit sie nicht ein zweites Mal inkludiert wird. Wenn Sie also eine eigene Header-Datei erstellt haben, sollten Sie immer Folgendes einfügen, damit die Datei nicht mehrmals inkludiert wird:
// myheader.h
#ifndef MYHEADER_H_
#define MYHEADER_H_
// Der eigentliche Quellcode für die Headerdatei myheader.h
#endif
Der Präprozessor überprüft, ob er die Header-Datei myheader.h bereits eingebunden hat. Beim ersten Inkludieren ist das Makro MYHEADER_H_ noch nicht definiert, sodass der Inhalt zwischen #ifndef und #endif in die Quelldatei eingefügt wird. Wird jetzt erneut myheader.h von einer anderen Datei inkludiert, ist #ifndef falsch. Alles zwischen #ifndef und #endif wird jetzt vom Präprozessor ignoriert.
Achtung: Probleme beim mehrfachen Inkludieren
Mehrfaches Inkludieren sollte vermieden werden, wenn Sie mehrere Header-Dateien und Module verwenden, weil dies zu diversen Compiler-Fehlern führen kann.
In Tabelle 8.2 finden Sie einen Überblick über die restlichen Präprozessor-Direktiven.
Direktive |
Beschreibung |
---|---|
Das Programm lässt sich nicht übersetzen und gibt die Fehlermeldung zeichenkette zurück. Damit können Sie verhindern, dass ein nicht fertiggestellter oder nicht fehlerfreier Code einfach so übersetzt wird. Das ist recht praktisch als Hinweis, wenn mehrere Personen an einem Programm arbeiten. |
|
Diese Direktive hat keinen Einfluss auf das Programm selbst. Damit können Sie beispielsweise die Zeilennummer in einem Programm mit n festlegen und den Dateinamen auf dateinamen setzen. Die Nummerierung hat dann Einfluss auf die vordefinierten Präprozessor-Direktiven __LINE__ und __FILE__. |
|
#pragma-Direktiven sind compilerspezifisch, also von Compiler zu Compiler verschieden. Wenn ein Compiler eine bestimmte #pragma-Direktive nicht kennt, wird diese ignoriert. Mithilfe der Pragmas können beispielsweise Compiler-Optionen definiert werden, ohne mit anderen Compilern in Konflikt zu geraten. |
|
Damit Sie mit pragma auch Makros verwenden können, wurde mit dem C99-Standard der Operator _Pragma( bezeichner) eingeführt. |
|
# |
Ein leeres # wird vom Präprozessor entfernt und hat keine Auswirkung im Programm. |
Des Weiteren gibt es noch einige vordefinierte Makros, die u. a. für das Debuggen von Programmen recht nützlich sein können.
Makroname |
Beschreibung |
---|---|
Gibt eine Ganzzahl der aktuellen Zeilennummer in der Programmdatei zurück. |
|
Gibt den Namen der Programmdatei als String-Literal zurück. |
|
Gibt das Übersetzungsdatum der Programmdatei als String-Literal zurück. |
|
Gibt die Übersetzungszeit der Programmdatei als String-Literal zurück. |
|
Besitzt diese ganzzahlige Konstante den Wert 1, wurde das Programm mit dem Standard kompiliert. |
|
Gibt den Namen der Funktion aus, in der das Makro verwendet wird (seit C99). |
|
Enthält eine ganzzahlige Konstante vom Typ long, die den Standard beschreibt, mit dem das Programm kompiliert wurde. Der Wert 199409L steht für C90, 199901L für C99 und 201112L für C11. |
Hier ein Beispiel, das einige dieser vordefinierten Makros im Einsatz zeigt:
00 // Kapitel8/C99_makros.c
01 #include <stdio.h>
02 #include <stdlib.h>
03 #if __STDC_VERSION__ >= 199901L
04 void eineFunktion(void) {
05 printf("Name der Funktion: %s\n", __func__);
06 }
07 #else
08 void eineFunktion(void) {
09 printf("Compiler kann kein C99\n");
10 }
11 #endif
12 int main(void) {
13 #if __STDC_VERSION__ >= 201112L
14 printf("Die Ausrichtung von char: %zd\n",_Alignof(char));
15 #else
16 printf("Compiler kann kein C11\n");
17 #endif
18 printf("Datum der Uebersetzung: %s\n", __DATE__);
19 printf("Zeit der Uebersetzung : %s\n", __TIME__);
20 printf("Zeile: %3d | Datei: %s\n", __LINE__, __FILE__);
21 eineFunktion();
22 return EXIT_SUCCESS;
23 }
Das Formatelement %s wurde noch nicht behandelt, aber in diesem Beispiel ist die Verwendung nötig. Sie verwenden es in printf(), wenn eine String-Variable (also eine Zeichenkette) als Parameter übergeben wird. Das Umwandlungszeichen s des Formatstrings steht für einen String. Im letzten Listing geben beispielsweise die vordefinierten Makros wie __DATE__, __TIME__, __FILE__ eine String-Variable mit entsprechenden Inhalt zurück.
Das Programm gibt bei der Ausführung Folgendes aus:
Die Ausrichtung von char: 1
Datum der Uebersetzung: Nov 25 2015
Zeit der Uebersetzung : 17:06:40
Zeile: 24 | Datei: Projects/listing008/listing006.c
Name der Funktion: eineFunktion