#define

Mittels der #define-Direktive wird einem Namen ein Makro zugewiesen, welches im gesamten darauf folgenden Code den Namen ersetzt. Makros können Parameter besitzen, die im Code ähnlich wie Funktionen aufgerufen werden können. Der Gebrauch solcher Makros ist jedoch heimtückisch.

#include <stdio.h> #include <math.h> #define PI 3.14 #define TRUE_LOVE "ManderC" #define PYTHAGORAS(a,b) sqrt(a*a+b*b) int main(){ printf("PI = %f\n", PI); printf("I love %s\n", TRUE_LOVE); printf("Hypothenuse = %f\n", PYTHAGORAS(3,4)); return 0; }

PI = 3.140000 I love ManderC Hypothenuse = 5.000000

Siehe auch #undef, #ifdef, #if, defined

Details

Makros sind sehr gut geeignet für allgemeingültige Konstanten wie Pi, e, Wurzel-2, die Erdanziehungskraft g... Solche und ähnliche unveränderbare konstanten Werte sind insofern bevorzugt als Makros zu definieren, da sie nicht wie eine Variable während dem Programmablauf erst aus dem Speicher gelesen werden müssen, sondern direkt im Programmcode compiliert sind. Des weiteren werden Makros oftmals zur bedingten Compilierung verwendet, beispielsweise mit Flags wie #define USE_METHOD 4. Makros können jedoch auch das Programmieren selbst vereinfachen, da sie an fast jeder beliebigen Stelle eingesetzt werden können und somit mühselige Schreibarbeit verhindern.

Da Makros jedoch nicht unproblematisch sind, wird empfohlen, ein eindeutiges Erkennungsmerkmal für Makros, üblicherweise GROSSSCHREIBUNG, zu verwenden, sowie möglichst kleine, überschaubare Makros zu schreiben. Makros sind sehr anfällig auf Nebeneffekte, weswegen empfohlen wird, Parameter von parametrisierten Makros nicht zu beschreiben (beispielsweise mittels Inkrement oder Dekrement), sowie Berechnungen, Casts und Dereferenzierungen immer mit Klammern zu umschliessen: (PI/2.f), ((int)floor(PI)), (*(ptr)). Siehe dazu die Beispiele weiter unten.

Parametrisierte Makros können mit den Auslassungspunkten ... auch Parameter variabler Länge besitzen.

Ein Makro muss in einer einzelnen Zeile definiert werden. Mehrzeilige Makros können mittels einem Backslash \ am Ende der Zeile erreicht werden.

Makros werden erst dann ausgewertet, wenn sie gebraucht werden, jedes Mal von neuem:

#define FOURTY_TWO UNDEFINED_NUMBER FOURTY_TWO #define UNDEFINED_NUMBER 42 FOURTY_TWO

UNDEFINED_NUMBER 42

Makros können keine weiteren Direktiven enthalten. Sowohl das führende #, als auch der Name der Direktive werden durch den Preprozessor nicht aufgelöst:

#define DEFINE_PI #define PI 3.14 DEFINE_PI

expected unqualified-id before '#' token

Der Fehler, der hierbei entsteht, ist verwirrend. Er entsteht durch den Compiler, nicht durch den Preprozessor. Das obige Beispiel wird in folgende Zeile übersetzt:

#define PI 3.14

Daraufhin jedoch beendet der Preprozessor seinen Dienst und übergibt diesen Code an den eigentlichen Compiler, der mit dem # nichts anfangen kann.

Verschluckung des Semikolons ;

Da der Preprozessor Zeichen für Zeichen umwandelt, ist es nicht notwendig, ein Makro mit einem Semikolon ; abzuschliessen, wenn man es im eigentlichen Code wie eine Funktion aussehen lassen möchte.

#define PYTHAGORAS_1(a,b) sqrt(a*a+b*b); #define PYTHAGORAS_2(a,b) sqrt(a*a+b*b) PYTHAGORAS_1(3,4); PYTHAGORAS_2(3,4);

sqrt(3*3 +4*4);; sqrt(3*3 +4*4);

Diese Massnahme ist bekannt unter dem Begriff Verschluckung des Semikolons. Wenn ein Makro definiert wird, welches eine Funktion beschreibt, welche einen Wert zurückgibt, so ist das Semikolon sogar äusserst problematisch, da ansonsten das Semikolon in den Code mit hineinkopiert wird, was zu einem Syntax-Fehler führt.

printf("Hypothenuse: %d", PYTHAGORAS_1(3,4));

printf("Hypothenuse: %d", sqrt(3*3 +4*4););

Die Verschluckung des Semikolons beschreibt zudem ein weiteres Phänomen, welches mit der Bereichs-Klammerung mit geschweiften Klammern {} und der Benutzung des Makro vor einem else-Statement einer einzeiligen if-Bedingung zu tun hat. Bereichs-Klammerung ging jedoch heutzutags aufgrund der Veränderung des Programmierstils fast gänzlich verloren, weswegen für dieses Phänomen auf weitere Quellen verwiesen wird.

Nebeneffekte

Der Gebrauch von Makros ist nicht bei jedem Programmierer gleich beliebt. Oftmals geht vergessen, dass Makros nicht wie Variablen oder Funktionen ausgelesen oder aufgerufen werden, sondern durch den Preprozessor Zeichen für Zeichen direkt in den Programmcode hineingeschrieben werden. Folgendes Programm verdeutlicht die Problematik:

#include <stdio.h> void print_twice(int v){ printf("%d, %d\n", v, v);} #define PRINT_TWICE(v) printf("%d, %d\n", v, v) int main(){ print_twice(random()); PRINT_TWICE(random()); return 0; }

1804289383, 1804289383 1681692777, 846930886

Während bei der ersten Variante zweimal derselbe Wert ausgegeben wird, erhält man bei der Makro-Variante zwei verschiedene Werte, obschon das Makro und die Funktion denselben Code definieren. Der Unterschied besteht darin, dass bei der ersten Variante zuerst die Funktion random() aufgerufen wird und das Resultat (Eine Zufallszahl) als Parameter übergeben wird. Bei der Makro-Definition hingegen wird der Code random() selbst übergeben, was der Preprozessor schlussendlich in folgende Zeile umwandelt:

printf("%d, %d\n", random(), random());

Dies bedeutet, dass random() zweimal aufgerufen wird.

Klammerung von Berechnungen

Für den Preprozessor oder Compiler gibt es feste Regeln, in welcher Reihenfolge Operanden umgesetzt werden. Der Programmierer kann diese Reihenfolge unter anderem mit Klammerung steuern. Bei Makros wird empfohlen, um Berechnungen herum stets Klammern zu setzen. Folgendes Beispiel zeigt, was passiert, wenn man eine Berechnung nicht korrekt klammert:

#include <stdio.h> #define PI_HALF_1 3.14/2.0 #define PI_HALF_2 (3.14/2.0) int main(){ printf("1 / PI_HALF: %f\n", 1. / PI_HALF_1); printf("1 / PI_HALF: %f\n", 1. / PI_HALF_2); return 0; }

1 / PI_HALF: 0.159236 1 / PI_HALF: 0.636943

Der Preprozessor übersetzt den Code in die folgenden Zeilen:

printf("1 / PI_HALF: %f\n", 1. / 3.14/2.0 ); printf("1 / PI_HALF: %f\n", 1. / (3.14/2.0));

In der ersten Zeile wird zuerst 1 / PI gerechnet und danach das Resultat durch 2 geteilt. Korrekt wäre jedoch, dass wie in der zweiten Zeile zuerst PI / 2 berechnet wird, und vom Resultat dann der Kehrwert genommen wird.

Klammerung von Casts

Oftmals werden in Makros explizite Casts gemacht. In den allermeisten Fällen wird man damit keine Probleme haben, allerdings zeigt folgendes Programm ein einfaches Beispiel dessen, was passieren könnte:

#include <stdio.h> #define INTEGRAL_1(x) (int) x #define INTEGRAL_2(x) (int)(x) int main(){ float a = 1.5; float d; d = INTEGRAL_1(a + 1.5f); printf("Int of 1.5 + 1.5 is %f\n", d); d = INTEGRAL_2(a + 1.5f); printf("Int of 1.5 + 1.5 is %f\n", d); return 0; }

Int of 1.5 + 1.5 is 2.500000 Int of 1.5 + 1.5 is 3.000000

Die beiden Zeilen mit der Berechnung werden vom Preprozessor folgendermassen umgewandelt:

d = (int) a + 1.5f; d = (int)(a + 1.5f);

Damit ist klar, dass bei der ersten Zeile nur a gecastet wird, wohingegen auf der zweiten Zeile die gesamte Berechnung a + 1.5 in einen Integer umgewandelt wird.

Die Fehler bei vergessener Klammerung von Casts können noch schlimmer ausfallen, sobald sie im Zusammenhang mit Pointer-Umwandlungen gebraucht werden, das Programm kann hierbei sogar abstürzen. es ist zu empfehlen, um das gesamte Makro zusätzliche Klammern hinzuzufügen. Folgendes Beispiel versucht, ein zweidimensionales Array als eindimensionales zu casten:

#include <stdio.h> #define CASTING_1(x) (int*) x #define CASTING_2(x) (int*)(x) #define CASTING_3(x) ((int*)(x)) int main(){ int a[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; printf("Number: %i\n", CASTING_1(a)[1]); printf("Number: %i\n", CASTING_2(a)[1]); printf("Number: %i\n", CASTING_3(a)[1]); return 0; }

Number: -1073743256 Number: -1073743256 Number: 2

Die Zeilen werden vom Preprozessor folgendermassen umgewandelt:

printf("Number: %i\n", (int*) a [1]); printf("Number: %i\n", (int*)(a) [1]); printf("Number: %i\n", ((int*)(a))[1]);

Während in den ersten beiden Zeilen strickt nach der von der Programmiersprache vorgegebenen Operatorenreihenfolge zuerst das Element mit Index [1] angesprochen und danach fälschlicherweise dereferenziert wird (was zu einer Zahl im Nirvana des unbenutzten Speichers führt), wird erst in der dritten Zeile korrekterweise das Array zuerst als eindimensionales Array gecastet und danach das Element mit Index [1] angesprochen.

Klammerung von Dereferenzen

Folgendes Beispiel zeigt, was passiert, wenn man eine Dereferenzierung nicht korrekt klammert:

#include <stdio.h> #define VALUE_1 (*numbers) #define VALUE_2 *numbers int main(){ int numbers[3][3] = {1,2,3,4,5,6,7,8,9}; printf("Number: %d\n", VALUE_1[1]); printf("Number: %d\n", VALUE_2[1]); return 0; }

Number: 2 Number: 4

Der Preprozessor übersetzt den Code in die folgenden Zeilen:

printf("Number: %d\n", (*numbers)[1]); printf("Number: %d\n", *numbers [1]);

Während bei der ersten Zeile zuerst das erste Dreier-Tupel {1,2,3} dereferenziert wird und danach das Element 2 mit Index [1] ausgewählt wird, wird bei der zweiten Zeile zuerst das Dreier-Tupel {4,5,6} mit Index [1] ausgewählt und dann das erste Element 4 dereferenziert.

 

#include<< Zurück zum Index >>#undef