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

#if ausdruck

Wird ausdruck erfüllt (ungleich 0), wird der darauffolgende Programmteil ausgeführt.

#ifdef symbol
#if defined symbol
#if defined(symbol)

Ist symbol im Programm definiert, wird der darauffolgende Programmteil ausgeführt.

#ifndef symbol
#if !defined symbol
#if !defined(symbol)

Ist symbol im Programmteil nicht definiert, wird der darauffolgende Programmteil ausgeführt.

#elif ausdruck
#elif symbol

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.

#else

Der alternative Programmteil dahinter wird ausgeführt, wenn ein vorausgehendes #if, #ifdef, #ifndef oder #elif nicht ausgeführt wird.

#endif

Zeigt das Ende der bedingten Kompilierung an.

Tabelle 8.1    Schlüsselwörter zur bedingten Kompilierung

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 }

Listing 8.5    Durch die bedingte Kompilierung läuft dieses Listing sowohl auf 32-Bit-Systemen als auch auf 16-Bit-Systemen korrekt.

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

#error "zeichenkette"

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.

#line n
#line n "dateiname"

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

#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.

#pragma

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.

Tabelle 8.2    Die restlichen Präprozessor-Direktiven

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

__LINE__

Gibt eine Ganzzahl der aktuellen Zeilennummer in der Programmdatei zurück.

__FILE__

Gibt den Namen der Programmdatei als String-Literal zurück.

__DATE__

Gibt das Übersetzungsdatum der Programmdatei als String-Literal zurück.

__TIME__

Gibt die Übersetzungszeit der Programmdatei als String-Literal zurück.

__STDC__

Besitzt diese ganzzahlige Konstante den Wert 1, wurde das Programm mit dem Standard kompiliert.

__func__

Gibt den Namen der Funktion aus, in der das Makro verwendet wird (seit C99).

__STD_VERSION__

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.

Tabelle 8.3    Vordefinierte Standardmakros

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 }

Listing 8.6    C99_makros.c demonstriert einige vordefinierte Makros seit C99.

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