8.2    Konstanten und Makros mit #define und #undef

Mit #define ist es möglich, Texte anzugeben, die vor der Übersetzung des Programms durch andere Texte ersetzt werden. Auch hier bewirkt das Zeichen # vor define, dass der Präprozessor zuerst seine Arbeit verrichtet, in dem Fall die Textersetzung. Erst danach wird das werdende Programm vom Compiler in Maschinensprache übersetzt. Die Syntax der define-Direktive sieht so aus:

#define BEZEICHNER Ersatzbezeichner
#define BEZEICHNER(Bezeichner_Liste) Ersatzbezeichner

Bei der ersten Syntaxbeschreibung wird eine symbolische Konstante und im zweiten Fall ein Makro definiert. In der Praxis werden für die symbolischen Konstanten oder für ein Makro für gewöhnlich Großbuchstaben verwendet, um sie sofort von normalen Variablen unterscheiden zu können. Dies ist aber keine feste Regel. Für die Namen gelten dieselben Regeln wie schon bei den Bezeichnern (siehe Abschnitt 2.4.1, »Bezeichner«).

8.2.1    Symbolische Konstanten mit #define definieren

Es folgt nun ein erstes Programmbeispiel, das eine symbolische Konstante definiert:

00  // Kapitel8/define_1.c
01 #include <stdio.h>
02 #include <stdlib.h>
03 #define WERT 1234

04 int main(void) {
05 int val = WERT * 2;
06 printf("%d\n", WERT);
07 printf("%d\n", val);
08 return EXIT_SUCCESS;
09 }

Listing 8.1    Dieses einfache Beispiel definiert eine symbolische Konstante WERT mit dem Wert 1234.

In Listing 8.1 wird die symbolische Konstante WERT mit dem Wert 1234 in Zeile (03) definiert. Wenn Sie das Programm übersetzen, werden vor der Kompilierung alle Namen mit WERT im Quelltext (hier in Zeile (05) und (06)) vom Präprozessor durch 1234 ersetzt. Erst wenn die Konstante wirklich im Programm auftritt, wird der entsprechende Speicher dafür angelegt.

Symbolische Konstanten sind unantastbar

Beachten Sie, dass der Compiler selbst nie etwas von der symbolischen Konstante zu sehen bekommt, weil der Präprozessor ihren Namen vor dem Compiler-Lauf durch den Wert der Konstanten ersetzt. Daher sollte Ihnen bewusst sein, dass #define-Makros echte Konstanten sind, die Sie zur Laufzeit des Programms nicht mehr ändern können. Anders als bei dem Schlüsselwort const sind dies also keine Variablen.

Der Vorteil solcher define-Konstanten liegt vorwiegend darin, dass das Programm besser lesbar ist und besser gewartet werden kann. Haben Sie z. B. eine symbolische Konstante in einem 10.000-Zeilen-Programm, etwa

#define ROW 15
#define COLUMN 80
#define TITLE "Mein Texteditor 0.1"

und wollen Sie diese Angaben ändern, müssen Sie nur den Wert in der define-Anweisung anpassen. Hätten Sie hier keine symbolische Konstante verwendet, müssten Sie im kompletten Quelltext (der meistens noch in mehrere Quelldateien aufgeteilt ist) nach den Werten 15, 80 und der Zeichenkette "Mein Texteditor 0.1" suchen und diese gegen die neuen Werte austauschen. Eine mühevolle und – falls Sie einen Wert vergessen – auch eine fehleranfällige Angelegenheit.

Hierzu ein weiteres Beispiel:

00  // Kapitel8/define_2.c
01 #include <stdio.h>
02 #include <stdlib.h>
03 #define PI 3.1415

04 double Kflaeche( double d ) {
05 return d * d * PI / 4;
06 }

07 double Kumfang( double d) {
08 return d * PI;
09 }

10 int main(void) {
11 double d = 0.0;
12 printf("Kreisdurchmesser: ");
13 if( scanf("%lf", &d) != 1 ) {
14 printf("Fehler bei der Eingabe...\n");
15 return EXIT_FAILURE;
16 }
17 printf("Kreisflaeche von %.2f = %.2f\n",d, Kflaeche(d));
18 printf("Kreisumfang von %.2f = %.2f\n",d, Kumfang(d));
19 return EXIT_SUCCESS;
20 }

Listing 8.2    define_2.c definiert für Kreisberechnungen die Zahl PI als Konstante.

In diesem Beispiel werden für einen gegebenen Kreisdurchmesser die Kreisfläche (Zeilen (04) bis (06)) und der Kreisumfang (Zeilen (07) bis (09)) berechnet. Für beide Kreisberechnungen wird die Angabe von Pi benötigt. Im Listing wurde PI als symbolische Konstante in Zeile (03) definiert und in den Berechnungen der Zeilen (05) und (08) verwendet. Sollten Sie jetzt eine höhere Genauigkeit für PI benötigen, brauchen Sie nur den Wert in Zeile (03) zu ändern. Sie müssen nicht im gesamten Programm danach suchen.

Das Programm gibt bei der Ausführung z. B. Folgendes aus:

Kreisdurchmesser: 5.5
Kreisflaeche von 5.50 = 23.76
Kreisumfang von 5.50 = 17.28

Natürlich ist es auch möglich, sich eine Konstante errechnen zu lassen. Die Konstante PI beispielsweise können Sie auch wie folgt definieren:

#include <math.h> // benötigter Header für acos()
#define PI acos(-1)

Wird das Programm ausgeführt, steht dort für PI jedes Mal acos(-1). Das führt aber leider dazu, dass dieser Wert jedes Mal da, wo PI durch die Berechnung ersetzt wurde, erneut berechnet werden muss. Daher sollten Sie für solche Berechnungen besser eine const-Variable verwenden. Mit dieser muss nur einmal gerechnet werden:

#include <math.h> // benötigter Header für acos()
const double PI = acos(-1)

8.2.2    Makros mit #define definieren

Neben symbolischen Konstanten lassen sich mit der define-Direktive auch parametrisierte Makros erstellen. Hierzu ein Beispiel:

00  // Kapitel8/makros.c
01 #include <stdio.h>
02 #include <stdlib.h>
03 #define NOT_EQUAL(x, y) ((x) != (y))
04 #define XCHANGE(x, y) { \
int j; j=(x); (x)=(y); (y)=j; }
05 int main(void) {
06 int ival1 = 0, ival2 = 0;
07 printf("Bitte zwei Ganzzahlwerte eingeben: ");
08 if( scanf("%d %d", &ival1, &ival2) != 2 ) {
09 printf("Fehler bei der Eingabe...\n");
10 return EXIT_FAILURE;
11 }
12 if( NOT_EQUAL(ival1, ival2) ) {
13 XCHANGE(ival1, ival2);
14 }
15 printf("val1: %d - val2: %d\n", ival1, ival2);
16 return EXIT_SUCCESS;
17 }

Listing 8.3    makros.c verwendet Makros, um zwei Zahlen miteinander zu vergleichen oder diese zu vertauschen.

Parametrisierte Makros erkennen Sie daran, dass nach dem entsprechenden Namen eine Klammer folgt, wie dies in den Zeilen (03) und (04) der Fall ist. Ein Makro ist im Gegensatz zu einer symbolischen Konstanten eine wirkliche kleine Funktion, also eine Berechnung. Bei einem Makro können Sie deshalb Parameter (auch mehrere davon), durch ein Komma voneinander getrennt, übergeben. Beide Makros haben in dem letzten Beispiel die formalen Parameter x und y. In Zeile (12) wird das Makro NOT_EQUAL verwendet. Nach dem Präprozessor-Lauf sieht die Zeile wie folgt aus:

12   if( ival1 != ival2 ) {

Trifft es bei der Ausführung der Zeile (12) zu, dass beide Werte nicht gleich sind, werden die zwei Werte getauscht. Die Zeile (13) im Quelltext wird vom Präprozessor wie folgt ersetzt:

13    int j; j=ival1; ival1=ival2; ival2=j; }

Zeilenumbrüche in Makros müssen Sie wie in Zeile (04) mit einem Backslash-Zeichen am Ende herbeiführen, weil sonst das Makro am Zeilenende beendet würde.

Makros sind also keine inline-Funktionen, sondern werden in jedem Fall direkt in den Quellcode hineinkopiert, egal wie der Compiler eingestellt ist. Makros beschleunigen also auf jeden Fall die Programmausführung. Natürlich sollten Sie auch Makros sparsam und gezielt einsetzen und auch hier normalen Funktionen den Vorrang geben.

Keine Typprüfung bei Makros

An dieser Stelle möchten wir noch einen Warnhinweis für parametrisierte Makros einfügen. Bedenken Sie, dass bei solchen Makros keine Typprüfung stattfindet. So können Sie das Makro NOT_EQUAL aus Zeile (03) des Beispiels makros.c auch mit anderen Typen wie float oder gar Zeigern aufrufen. Wo bei einfachen Datentypen »nur« die Gefahr besteht, dass einfach nur »Datenmüll« aus den Werten gemacht wird, kann dies bei Zeigern schwerwiegendere Folgen haben, bis hin zum Absturz des Programms.

Sie sollten sich außerdem angewöhnen, die Parameter im Ersatztext in Klammern zu setzen. Ein einfaches Beispiel verdeutlicht, was sonst passieren kann:

00  // Kapitel8/fehlerhaftes_makro.c
01 #include <stdio.h>
02 #include <stdlib.h>
03 #define MINUS(a, b) (a-b)

04 int main(void) {
05 printf("%f\n", MINUS(5.0, 2.5+0.5) );
06 return EXIT_SUCCESS;
07 }

Listing 8.4    Das Listing ist ein Beispiel für die fehlerhafte Verwendung von Makros, die Sie möglichst vermeiden sollten.

Durch die Ersetzung der Zeile (03) in Zeile (05) sieht die Berechnung wie folgt aus:

5.0-2.5+0.5

Als Ergebnis erhalten Sie hier 3.0. Das korrekte Ergebnis wäre allerdings 2.0, weil die Berechnung eigentlich 5.0–(2.5+0.5) lauten müsste.

Ausdrücke in Klammern

Wenn der Wert einer Konstanten keine einfache Zahl ist, sondern ein Ausdruck, sollten Sie den Parameter im Ersetzungstext immer zwischen Klammern setzen.

Um also wirklich sicherzugehen, dass auch solche Teilausdrücke richtig ersetzt werden, sollten Sie die Parameter im Ersatztext immer in Klammern setzen. In diesem Beispiel sollte die define-Anweisung der Zeile (04) deshalb folgendermaßen aussehen:

04  #define MINUS(a, b) ( (a) - (b) )
Funktionen oder besser doch Makros?

Wenn Sie anhand der letzten Abschnitte ein wenig Zweifel bekommen haben, ob Sie Makros mit Parametern verwenden sollten, sind diese berechtigt. Da bei Makros keinerlei Typprüfung durchgeführt wird und viele weitere Stolperstellen vorhanden sind, sollten Sie immer bevorzugt auf Funktionen zurückgreifen. Natürlich gibt es immer Ausnahmen, z. B. wenn Sie viele Funktionen programmiert haben, in denen in leicht abgewandelter Form immer wieder dieselben Zeilen stehen. Solche Programme sind aber eher die Ausnahme und treten nur in speziellen Situationen auf, z. B. bei der Programmierung von Prozessor-Emulatoren. Daher unser Tipp: Bevorzugen Sie Funktionen gegenüber Makros.

8.2.3    Symbolische Konstanten und Makros aufheben (#undef)

Sollen eine symbolische Konstante oder ein Makro ab einer bestimmten Stelle im Programm nicht mehr gültig sein, können Sie sie mit der undef-Anweisung wie folgt aufheben:

#undef BEZEICHNER

Möchten Sie hingegen nach der undef-Anweisung erneut auf BEZEICHNER zugreifen, beschwert sich der Compiler, dass dieser Bezeichner nicht deklariert wurde.

Entfernen Sie eine Konstante mit undef, die gar nicht existiert, wird diese Anweisung vom Präprozessor ignoriert. Sie hat somit keine Auswirkung auf das Programm.

In der Praxis haben Sie mit undef die Möglichkeit, die Gültigkeit für ein mit define eingeführtes Makro auf einen bestimmten Programmabschnitt einer Datei zu begrenzen.