
jfm3
In vielen C/C++-Makros sehe ich den Code des Makros in etwas eingehüllt, was bedeutungslos erscheint do while
Schleife. Hier sind Beispiele.
#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else
Ich kann nicht sehen, was do while
macht. Warum schreibst du das nicht einfach ohne?
#define FOO(X) f(X); g(X)

jfm3
Die do ... while
und if ... else
sind dazu da, damit ein Semikolon nach Ihrem Makro immer dasselbe bedeutet. Nehmen wir an, Sie hatten so etwas wie Ihr zweites Makro.
#define BAR(X) f(x); g(x)
Nun, wenn Sie verwenden würden BAR(X);
in einem (n if ... else
-Anweisung, bei der die Körper der if-Anweisung nicht in geschweiften Klammern stehen, würden Sie eine böse Überraschung erleben.
if (corge)
BAR(corge);
else
gralt();
Der obige Code würde sich erweitern in
if (corge)
f(corge); g(corge);
else
gralt();
was syntaktisch falsch ist, da das else nicht mehr mit dem if verbunden ist. Es hilft nichts, Dinge innerhalb des Makros in geschweifte Klammern einzuschließen, weil ein Semikolon nach den geschweiften Klammern syntaktisch falsch ist.
if (corge)
{f(corge); g(corge);};
else
gralt();
Es gibt zwei Möglichkeiten, das Problem zu beheben. Die erste besteht darin, ein Komma zu verwenden, um Anweisungen innerhalb des Makros zu sequenzieren, ohne es seiner Fähigkeit zu berauben, sich wie ein Ausdruck zu verhalten.
#define BAR(X) f(X), g(X)
Die obige Version von bar BAR
erweitert den obigen Code in das Folgende, was syntaktisch korrekt ist.
if (corge)
f(corge), g(corge);
else
gralt();
Dies funktioniert nicht, wenn statt f(X)
Sie haben einen komplizierteren Codekörper, der in einen eigenen Block eingefügt werden muss, z. B. um lokale Variablen zu deklarieren. Im allgemeinsten Fall besteht die Lösung darin, so etwas wie zu verwenden do ... while
um zu bewirken, dass das Makro eine einzelne Anweisung ist, die ein Semikolon ohne Verwirrung akzeptiert.
#define BAR(X) do { \
int i = f(X); \
if (i > 4) g(i); \
} while (0)
Sie müssen nicht verwenden do ... while
mit dem man etwas kochen könnte if ... else
auch, obwohl wann if ... else
dehnt sich innerhalb eines aus if ... else
es führt zu einem “sonst baumeln“, wodurch ein vorhandenes Dangling-Else-Problem noch schwerer zu finden sein könnte, wie im folgenden Code.
if (corge)
if (1) { f(corge); g(corge); } else;
else
gralt();
Der Punkt ist, das Semikolon in Kontexten zu verbrauchen, in denen ein baumelndes Semikolon fehlerhaft ist. Natürlich könnte (und sollte) an dieser Stelle argumentiert werden, dass eine Deklaration besser wäre BAR
als eigentliche Funktion, nicht als Makro.
Zusammenfassend ist die do ... while
ist da, um die Mängel des C-Präprozessors zu umgehen. Wenn diese C-Styleguides Ihnen sagen, dass Sie den C-Präprozessor ablegen sollen, machen sie sich darüber Sorgen.

paercebal
Makros sind kopierte/eingefügte Textteile, die der Präprozessor in den echten Code einfügt; Der Autor des Makros hofft, dass die Ersetzung gültigen Code erzeugt.
Damit das gelingt, gibt es drei gute „Tipps“:
Helfen Sie dem Makro, sich wie echter Code zu verhalten
Normaler Code endet normalerweise mit einem Semikolon. Sollte der Benutzer Code anzeigen, der keinen 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 ausgibt, wenn das Semikolon fehlt.
Aber der wirklich gute Grund ist, dass der Autor des Makros irgendwann das Makro durch eine echte Funktion (vielleicht inliniert) ersetzen muss. So sollte das Makro Ja wirklich benimm dich wie einer.
Wir sollten also ein Makro haben, das ein Semikolon benötigt.
Geben Sie einen gültigen Code ein
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, wird dies problematisch:
if(bIsOk)
MY_MACRO(42) ;
Dieses Makro könnte wie folgt erweitert werden:
#define MY_MACRO(x) f(x) ; g(x)
if(bIsOk)
f(42) ; g(42) ; // was MY_MACRO(42) ;
Die g
Die Funktion wird unabhängig vom Wert von ausgeführt bIsOk
.
Das bedeutet, dass wir dem Makro einen Geltungsbereich hinzufügen müssen:
#define MY_MACRO(x) { f(x) ; g(x) ; }
if(bIsOk)
{ f(42) ; g(42) ; } ; // was MY_MACRO(42) ;
Geben Sie einen gültigen Code ein 2
Wenn das Makro so etwas ist:
#define MY_MACRO(x) int i = x + 1 ; f(i) ;
Wir könnten ein weiteres Problem im folgenden Code haben:
void doSomething()
{
int i = 25 ;
MY_MACRO(32) ;
}
Weil es sich wie folgt erweitern würde:
void doSomething()
{
int i = 25 ;
int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}
Dieser Code wird natürlich nicht kompiliert. Die Lösung verwendet also wieder einen Bereich:
#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.
Semikolon + Scope-Effekte kombinieren?
Es gibt ein C/C++-Idiom, das diesen Effekt erzeugt: Die do/while-Schleife:
do
{
// code
}
while(false) ;
Das do/while kann einen Bereich erstellen und somit den Code des Makros kapseln und benötigt am Ende ein Semikolon, wodurch es in Code erweitert wird, der eines benötigt.
Der Bonus?
Der C++-Compiler optimiert die do/while-Schleife weg, da die Tatsache, dass ihre Nachbedingung falsch ist, zur Kompilierzeit bekannt ist. Dies 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 richtig als erweitern
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 als weg kompiliert und optimiert
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
{
f(43) ; g(43) ;
}
// Etc.
}
@jfm3 – Du hast eine nette Antwort auf die Frage. Vielleicht möchten Sie auch hinzufügen, dass das Makro-Idiom auch das möglicherweise gefährlichere (weil es keinen Fehler gibt) unbeabsichtigte Verhalten mit einfachen ‘if’-Anweisungen verhindert:
#define FOO(x) f(x); g(x)
if (test) FOO( baz);
erweitert sich zu:
if (test) f(baz); g(baz);
was syntaktisch korrekt ist, also keinen Compilerfehler hat, aber die wahrscheinlich unbeabsichtigte Folge hat, dass g() immer aufgerufen wird.
Die obigen Antworten erklären die Bedeutung dieser Konstrukte, aber es gibt einen signifikanten Unterschied zwischen den beiden, der nicht erwähnt wurde. Tatsächlich gibt es einen Grund, das zu bevorzugen do ... while
zum if ... else
bauen.
Das Problem der if ... else
Konstrukt ist, dass dies nicht der Fall ist Gewalt Sie setzen das Semikolon. Wie in diesem Code:
FOO(1)
printf("abc");
Obwohl wir das Semikolon (aus Versehen) weggelassen haben, wird der Code erweitert zu
if (1) { f(X); g(X); } else
printf("abc");
und wird im Hintergrund kompiliert (obwohl einige Compiler möglicherweise eine Warnung für nicht erreichbaren Code ausgeben). Aber die printf
Anweisung wird niemals ausgeführt.
do ... while
Konstrukt hat kein solches Problem, da das einzige gültige Token nach dem while(0)
ist ein Semikolon.

Coeur
Erläuterung
do {} while (0)
und if (1) {} else
sind sicherzustellen, dass das Makro auf nur 1 Anweisung erweitert wird. Andernfalls:
if (something)
FOO(X);
würde erweitern zu:
if (something)
f(X); g(X);
Und g(X)
würde außerhalb der hingerichtet werden if
Steuererklärung. Dies wird bei der Verwendung vermieden do {} while (0)
und if (1) {} else
.
Bessere Alternative
Mit einem GNU Aussage Ausdruck (nicht Teil von Standard C), haben Sie einen besseren Weg als do {} while (0)
und if (1) {} else
um dies zu lösen, indem Sie einfach verwenden ({})
:
#define FOO(X) ({f(X); g(X);})
Und diese Syntax ist mit Rückgabewerten kompatibel (beachten Sie, dass do {} while (0)
ist nicht), wie in:
return FOO("X");
Es wird zwar erwartet, dass Compiler das wegoptimieren do { ... } while(false);
Schleifen, gibt es eine andere Lösung, die dieses Konstrukt nicht erfordern würde. Die Lösung ist die Verwendung des Komma-Operators:
#define FOO(X) (f(X),g(X))
oder noch exotischer:
#define FOO(X) g((f(X),(X)))
Während dies mit separaten Anweisungen gut funktioniert, funktioniert es nicht mit Fällen, in denen Variablen konstruiert und als Teil von verwendet werden #define
:
#define FOO(X) (int s=5,f((X)+s),g((X)+s))
Damit wäre man gezwungen, das Konstrukt do/while zu verwenden.

Isaak Schwabacher
Jens Gustedts P99-Präprozessorbibliothek (ja, die Tatsache, dass es so etwas gibt, hat mich auch umgehauen!) if(1) { ... } else
konstruieren Sie auf kleine, aber bedeutende Weise, indem Sie Folgendes definieren:
#define P99_NOP ((void)0)
#define P99_PREFER(...) if (1) { __VA_ARGS__ } else
#define P99_BLOCK(...) P99_PREFER(__VA_ARGS__) P99_NOP
Die Begründung dafür ist, dass im Gegensatz zu den do { ... } while(0)
bauen, break
und continue
funktionieren immer noch innerhalb des angegebenen Blocks, aber die ((void)0)
erzeugt einen Syntaxfehler, wenn das Semikolon nach dem Makroaufruf weggelassen wird, wodurch sonst der nächste Block übersprungen würde. (Eigentlich gibt es hier kein Problem mit “dangling else”, da die else
bindet sich an den Nächsten if
die im Makro enthalten ist.)
Wenn Sie an den Dingen interessiert sind, die mit dem C-Präprozessor mehr oder weniger sicher erledigt werden können, sehen Sie sich diese Bibliothek an.
10024700cookie-checkWarum scheinbar bedeutungslose Do-While- und If-Else-Anweisungen in Makros verwenden?yes
Für das Beispiel mit else würde ich einen Ausdruck von hinzufügen
void
Typ am Ende … wie ((leer)0).– Phil1970
8. März 2018 um 17:59 Uhr
Erinnerung daran, dass die
do while
Konstrukt ist nicht kompatibel mit return-Anweisungen, also dieif (1) { ... } else ((void)0)
Konstrukt hat kompatiblere Verwendungen in Standard C. Und in GNU C bevorzugen Sie das in meiner Antwort beschriebene Konstrukt.– Coeur
12. Dezember 2018 um 11:50 Uhr