935 Stimmen

Warum werden scheinbar sinnlose do-while- und if-else-Anweisungen in Makros verwendet?

In vielen C/C++-Makros sehe ich den Code des Makros in einem scheinbar bedeutungslosen 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 erkennen, was die do while tut. Warum schreiben Sie das nicht einfach ohne sie?

#define FOO(X) f(X); g(X)

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 die if (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.

982voto

jfm3 Punkte 35554

El do ... while y if ... else sind dazu da, dass ein Semikolon nach Ihrem Makro immer dasselbe bedeutet. Angenommen, Sie hätten etwas wie Ihr zweites Makro.

#define BAR(X) f(x); g(x)

Wenn Sie nun Folgendes verwenden würden BAR(X); in einem if ... else Anweisung, bei der die Körper der if-Anweisung nicht in geschweifte Klammern eingeschlossen sind, würden Sie eine böse Überraschung erleben.

if (corge)
  BAR(corge);
else
  gralt();

Der obige Code würde sich zu

if (corge)
  f(corge); g(corge);
else
  gralt();

was syntaktisch falsch ist, da das else nicht mehr mit dem if verbunden ist. Es ist nicht hilfreich, die Dinge innerhalb des Makros in geschweifte Klammern einzuschließen, da ein Semikolon nach der geschweiften Klammer syntaktisch nicht korrekt ist.

if (corge)
  {f(corge); g(corge);};
else
  gralt();

Es gibt zwei Möglichkeiten, das Problem zu lösen. Die erste besteht darin, ein Komma zu verwenden, um die Anweisungen innerhalb des Makros aneinander zu reihen, ohne ihm seine Fähigkeit zu nehmen, sich wie ein Ausdruck zu verhalten.

#define BAR(X) f(X), g(X)

Die obige Version von bar BAR erweitert den obigen Code zu folgendem, syntaktisch korrektem Code.

if (corge)
  f(corge), g(corge);
else
  gralt();

Dies funktioniert nicht, wenn anstelle von f(X) Sie haben einen komplizierteren Code, der in einem eigenen Block untergebracht werden muss, z. B. um lokale Variablen zu deklarieren. Im allgemeinsten Fall besteht die Lösung darin, etwas zu verwenden wie do ... while um das Makro zu einer einzigen Anweisung zu machen, 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 die do ... while könnten Sie etwas kochen mit if ... else auch, obwohl, wenn if ... else dehnt sich innerhalb eines if ... else führt es zu einem " Sonstiges ", wodurch ein bestehendes "Dangling else"-Problem noch schwieriger zu finden sein könnte, wie im folgenden Code.

if (corge)
  if (1) { f(corge); g(corge); } else;
else
  gralt();

Es geht darum, das Semikolon in Kontexten zu verwenden, in denen ein hängendes Semikolon falsch ist. Natürlich könnte (und sollte wahrscheinlich) an dieser Stelle argumentiert werden, dass es besser wäre, zu deklarieren BAR als eigentliche Funktion, nicht als Makro.

Zusammenfassend lässt sich sagen, dass die do ... while ist dazu da, die Unzulänglichkeiten des C-Präprozessors zu umgehen. Wenn diese C-Style-Guides sagen, dass man den C-Präprozessor weglassen soll, dann ist das die Art von Dingen, um die sie sich Sorgen machen.

0 Stimmen

In meiner eigenen Antwort fehlte das "else-Problem, wenn geschweifte Klammern und Semikolon", aber Sie haben das Scope-Problem vergessen, ebenso wie die Compiler-Optimierung von do/while(false)... :-p ...

36 Stimmen

Ist das nicht ein starkes Argument dafür, in if-, while- und for-Anweisungen immer geschweifte Klammern zu verwenden? Wenn Sie darauf achten, dies immer zu tun (wie es z.B. für MISRA-C erforderlich ist), verschwindet das oben beschriebene Problem.

25 Stimmen

Das Komma-Beispiel sollte lauten #define BAR(X) (f(X), g(X)) andernfalls kann der Vorrang der Operatoren die Semantik durcheinander bringen.

185voto

paercebal Punkte 78198

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.
}

7 Stimmen

Beachten Sie, dass sich durch die Umwandlung von Makros in Inline-Funktionen einige vordefinierte Standardmakros ändern, z. B. zeigt der folgende Code eine Änderung in FUNKTION y LINE : #include <stdio.h> #define Fmacro() printf("%s %d \n ", FUNKTION , LINE ) inline void Finline() { printf("%s %d \n ", FUNKTION , LINE ); } int main() { Fmacro(); Finline(); return 0; } (fettgedruckte Begriffe sollten von doppelten Unterstrichen umschlossen werden - schlechter Formatierer!)

8 Stimmen

Es gibt eine Reihe kleinerer, aber nicht ganz unwichtiger Probleme mit dieser Antwort. Zum Beispiel: void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; } ist nicht die richtige Erweiterung; die x in der Erweiterung sollte 32 sein. Eine komplexere Frage ist, wie die Erweiterung von MY_MACRO(i+7) . Eine weitere ist die Ausweitung der MY_MACRO(0x07 << 6) . Es gibt vieles, was gut ist, aber es gibt auch ein paar nicht gepunktete i's und nicht gekreuzte t's.

0 Stimmen

@Gnubie: Für den Fall, dass du immer noch hier bist und es noch nicht herausgefunden hast: Du kannst Sternchen und Unterstriche in Kommentaren mit Backslashes auslassen, wenn du also ein \_\_LINE\_\_ IMHO ist es besser, nur Code-Formatierung für Code zu verwenden; zum Beispiel, __LINE__ (was keine besondere Behandlung erfordert). P.S. Ich weiß nicht, ob das 2012 schon so war. Seitdem haben sie einige Verbesserungen am Motor vorgenommen.

57voto

Michael Burr Punkte 320591

@jfm3 - Sie haben eine schöne Antwort auf die Frage. Sie sollten vielleicht noch hinzufügen, dass das Makro-Idiom auch das möglicherweise gefährlichere (weil fehlerfreie) unbeabsichtigte Verhalten bei einfachen 'if'-Anweisungen verhindert:

#define FOO(x)  f(x); g(x)

if (test) FOO( baz);

erweitert sich auf:

if (test) f(baz); g(baz);

was syntaktisch korrekt ist, so dass es keinen Compilerfehler gibt, aber die wahrscheinlich unbeabsichtigte Folge hat, dass g() immer aufgerufen wird.

25voto

Yakov Galka Punkte 65787

Die obigen Antworten erklären die Bedeutung dieser Konstrukte, aber es gibt einen wesentlichen Unterschied zwischen den beiden, der nicht erwähnt wurde. In der Tat gibt es einen Grund, die do ... while à la if ... else konstruieren.

Das Problem der if ... else Konstrukts ist, dass es nicht Kraft Sie müssen das Semikolon setzen. Wie in diesem Code:

FOO(1)
printf("abc");

Obwohl wir das Semikolon (aus Versehen) weggelassen haben, wird der Code zu

if (1) { f(X); g(X); } else
printf("abc");

und wird stillschweigend kompiliert (obwohl einige Compiler eine Warnung für unerreichbaren Code ausgeben können). Aber die printf Anweisung wird nie ausgeführt.

do ... while Konstrukt hat dieses Problem nicht, da das einzige gültige Token nach dem while(0) ist ein Semikolon.

0 Stimmen

Deshalb sollten Sie #define FOO(X) if (1) { f(X); g(X); } else (void)0

3 Stimmen

@RichardHansen: Immer noch nicht so gut, weil man beim Blick auf den Makroaufruf nicht weiß, ob er zu einer Anweisung oder zu einem Ausdruck expandiert. Wenn jemand das Letztere annimmt, kann er schreiben FOO(1),x++; was wiederum zu einem falsch positiven Ergebnis führen wird. Verwenden Sie einfach do ... while und das war's.

1 Stimmen

Es sollte genügen, das Makro zu dokumentieren, um Missverständnisse zu vermeiden. Ich stimme zu, dass do ... while (0) ist vorzuziehen, hat aber einen Nachteil: A break o continue kontrolliert die do ... while (0) Schleife, nicht die Schleife, die den Makroaufruf enthält. Daher ist die if Trick hat immer noch einen Wert.

21voto

Cœur Punkte 34332

Erläuterung

do {} while (0) y if (1) {} else sind, um sicherzustellen, dass das Makro auf nur 1 Anweisung erweitert wird. Andernfalls:

if (something)
  FOO(X); 

erweitern würde:

if (something)
  f(X); g(X); 

Et g(X) würde außerhalb der if Kontrollanweisung. Dies wird vermieden, wenn Sie do {} while (0) y if (1) {} else .


Bessere Alternative

Mit einem GNU Anweisungsausdruck (nicht Teil von Standard C), haben Sie eine bessere Möglichkeit als do {} while (0) y if (1) {} else um dies zu lösen, indem man einfach ({}) :

#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");

5 Stimmen

Die Verwendung von Block-Klammern {} im Makro würde ausreichen, um den Makro-Code zu bündeln, so dass alles für denselben if-Bedingungspfad ausgeführt wird. das do-while around wird verwendet, um ein Semikolon an den Stellen zu erzwingen, an denen das Makro verwendet wird. dadurch wird erzwungen, dass sich das Makro funktionsähnlicher verhält. dies schließt die Anforderung für das abschließende Semikolon bei Verwendung ein.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X