Makros sind kopierte/eingefügte Textstücke, die der Präprozessor in den echten Code einfügt; der Autor des Makros hofft, dass die Ersetzung gültigen Code ergibt.
Es gibt drei gute "Tipps", um das zu erreichen:
Das Makro soll sich wie echter Code verhalten
Normaler Code wird in der Regel mit einem Semikolon abgeschlossen. Sollte der Benutzer Code sehen, der kein Semikolon benötigt...
doSomething(1) ;
DO_SOMETHING_ELSE(2) // <== Hey? What's this?
doSomethingElseAgain(3) ;
Das bedeutet, dass der Benutzer erwartet, dass der Compiler einen Fehler erzeugt, wenn das Semikolon fehlt.
Der wirklich gute Grund ist jedoch, dass der Autor des Makros das Makro vielleicht irgendwann durch eine echte Funktion (vielleicht inlined) ersetzen muss. Daher sollte das Makro wirklich sich wie ein solcher verhalten.
Wir sollten also ein Makro haben, das ein Semikolon benötigt.
Erzeugen Sie einen gültigen Code
Wie in der Antwort von jfm3 gezeigt, enthält das Makro manchmal mehr als eine Anweisung. Und wenn das Makro innerhalb einer if-Anweisung verwendet wird, ist dies problematisch:
if(bIsOk)
MY_MACRO(42) ;
Dieses Makro könnte erweitert werden als:
#define MY_MACRO(x) f(x) ; g(x)
if(bIsOk)
f(42) ; g(42) ; // was MY_MACRO(42) ;
El g
Funktion wird unabhängig vom Wert von bIsOk
.
Dies bedeutet, dass wir dem Makro einen Bereich hinzufügen müssen:
#define MY_MACRO(x) { f(x) ; g(x) ; }
if(bIsOk)
{ f(42) ; g(42) ; } ; // was MY_MACRO(42) ;
Erzeugen Sie einen gültigen Code 2
Wenn das Makro etwa so lautet:
#define MY_MACRO(x) int i = x + 1 ; f(i) ;
Im folgenden Code könnte ein weiteres Problem auftreten:
void doSomething()
{
int i = 25 ;
MY_MACRO(32) ;
}
Denn es würde sich ja ausweiten:
void doSomething()
{
int i = 25 ;
int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}
Dieser Code lässt sich natürlich nicht kompilieren. Die Lösung ist also wieder die Verwendung eines Bereichs:
#define MY_MACRO(x) { int i = x + 1 ; f(i) ; }
void doSomething()
{
int i = 25 ;
{ int i = 32 + 1 ; f(i) ; } ; // was MY_MACRO(32) ;
}
Der Code verhält sich wieder korrekt.
Kombination von Semikolon und Umfangseffekten?
Es gibt ein C/C++ Idiom, das diesen Effekt erzeugt: Die do/while-Schleife:
do
{
// code
}
while(false) ;
Die do/while-Anweisung kann einen Geltungsbereich erstellen, wodurch der Code des Makros gekapselt wird, und benötigt ein Semikolon am Ende, wodurch der Code erweitert wird, der eines benötigt.
Der Bonus?
Der C++-Compiler wird die do/while-Schleife wegoptimieren, da die Tatsache, dass die Nachbedingung falsch ist, zur Kompilierungszeit bekannt ist. Das bedeutet, dass ein Makro wie:
#define MY_MACRO(x) \
do \
{ \
const int i = x + 1 ; \
f(i) ; g(i) ; \
} \
while(false)
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
MY_MACRO(42) ;
// Etc.
}
wird korrekt expandiert als
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
do
{
const int i = 42 + 1 ; // was MY_MACRO(42) ;
f(i) ; g(i) ;
}
while(false) ;
// Etc.
}
und wird dann kompiliert und wegoptimiert als
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
{
f(43) ; g(43) ;
}
// Etc.
}
3 Stimmen
Für das Beispiel mit dem else würde ich einen Ausdruck von
void
am Ende eingeben... wie ((void)0) .3 Stimmen
Zur Erinnerung: Die
do while
Konstrukt ist nicht kompatibel mit Return-Anweisungen, so dass dieif (1) { ... } else ((void)0)
Konstrukt hat mehr kompatible Verwendungen in Standard C. Und in GNU C werden Sie das in meiner Antwort beschriebene Konstrukt bevorzugen.