Nehmen wir uns einmal ein kleines, unscheinbares Makro. Ich habe es in ein Foundation Tool als Projekt eingebaut.
Denken wir uns folgenden Code:
Makros.m
#import <Foundation/Foundation.h>
#define power3( a ) a * a * a
int main (int argc, const char * argv[]) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSLog( @"%d: %d", 2, power3( 2 ) );
[pool release];
return 0;
}
Wie gesagt, nix Dolles, funktioniert auch, wie sich bei einem Test zeigt.
Console
2008-11-19 22:07:09.211 Makros[10044] 2: 8
Funktioniert auch? Mal schauen …
Wenn wir ein Makro benutzen, müssen wir uns darüber klar werden, dass das Makro expandiert. Wir können das wie Textersatz machen. Nehmen wir ein einfaches Beispiel:
Makros.m
int main (int argc, const char * argv[]) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSLog( @"%d: %d", 2, 5 % power3( 2 ) );
[pool release];
return 0;
}
Wie wir vorhin gesehen hatten, evaluiert das Makro zu 8. Wir haben da jetzt also "eigentlich" 5 % 8, also 5 stehen. Starten und testen:
Console
2008-11-19 22:14:25.753 Makros[10155] 2: 4
Wie kommt das? Es liegt daran, dass ein Makro in den Aufrufer expandiert. Da steht also "in Wahrheit:"
Makros.m
NSLog( @"%d: %d", 2, 5 % 2 * 2 * 2 );
Und da der Modulo-Operator und die Multiplikation die gleiche Priorität haben, wird dies von links nach rechts ausgewertet,also zuerst 5 % 2, was bekanntlich 1 ergibt:
Makros.m
NSLog( @"%d: %d", 2, 1 * 2 * 2 );
Und dies ist nun offensichtlich 4.
Um wenigstens diesen schlimmsten Patzer zu vermeiden, sollte ein Makro stets nach außen geklammert sein:
Makros.m
#define power3( a ) (a * a * a)
Gerne übersehen wird auch, dass dies ebenfalls umgekehrt gilt. Der Parameter wird in das Makro expandiert. versuchen wir es einmal
Makros.m
NSLog( @"%d: %d", 2, power3( 1 + 1 ) );
Statt zwei schreiben wir also 1+1. Sollte ja eigentlich keinen Unterschied machen, nicht wahr? Testen:
Console
2008-11-19 22:22:27.406 Makros[10343] 2: 4
Wieso ergibt den power3( 2 ) wie erwartet 8, power3( 1 + 1 ) jedoch 4? Auch hier gilt wieder die Regel vom Textersatz, diesmal umgekehrt.Aus dem Makro wird also
Makros.m
NSLog( @"%d: %d", 2, 1 + 1 * 1 + 1 * 1 + 1 );
Und dies ist eindeutig 4, nicht 8. Hier schlagen wieder die Operatorenregeln zu: * bindet stärker als +. Dass das 1+1 mal ein "Parameter" war, erkennt der Compiler nicht mehr.
Wenn man also schon Makros benutzen will, was man nicht will, so muss auch der Parameter gekapselt werden:
Makros.m
#define power3( a ) ((a) * (a) * (a))
Es gibt verschiedene Arten, Parameter zu übergeben. Call-By-Value und Call-By-Reference – was es nicht wirklich, sondern über einen Trick in C gibt – sollten bekannt sein. Aber was macht eigentlich ein Makro? Es wird der Sourcetext eingesetzt, wie wir gerade schon sahen. Das hat aber noch ganz andere Auswirkungen:
Makros.m
int main (int argc, const char * argv[]) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
int b = 2;
NSLog( @"%d: %d", 2, power3( ++b ) );
NSLog( @"%d", b );
[pool release];
return 0;
}
Bei mir auf dem G4 führt da zu
Console
2008-11-19 22:42:40.541 Makros[10565] 2: 80
2008-11-19 22:42:40.543 Makros[10565] 5
Aber keine Sorge, wenn das bei Ihnen anders steht. Das kann gut sein. In Wahrheit haben wir nämlich ein undefiniertes Ergebnis. Um zu verstehen, wie es dazu kommt, schauen wir uns zunächst die zweite Zeile an.Wieso ist b nach der Ausführung des Makros 5 und nicht 3?
Auch hier gilt wieder die Expansion:
Makros.m
NSLog( @"%d: %d", 2, ++b * ++b * ++b );
Es wird also dreimal inkrementiert: 2->3->4->5.
Und jetzt ist dem C-Kennner auch klar, warum 80 herauskommen kann. Bei einem Preinkrement ist der Compiler lediglich verpflichtet, das Inkrement vordemLesen der Variable auszuführen. In diesem Falle entscheidet er sich dazu, bereits zwei Preinkrements vorher auszuführen. das führt dazu,dass b 4 ist. Das letzte Preinkrement führt er dann – auch hierzu ist er berechtigt –, vor dem Letzten b aus, so dass dann b 5 ist: 4* 4 * 5 = 80.
Es gibt noch viel mehr Probleme bei Makros: Zu nennen wären hier die schlechtere Debuggerunterstützung und die fehlende Typisierung. Man sollte sie eigentlich gar nicht benutzen.