Warum scheinbar bedeutungslose Do-While- und If-Else-Anweisungen in Makros verwenden?

Lesezeit: 12 Minuten

Warum scheinbar bedeutungslose Do While und If Else Anweisungen in Makros verwenden
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)

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

Warum scheinbar bedeutungslose Do While und If Else Anweisungen in Makros verwenden
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 ... whilemit 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.

  • Ist das nicht ein starkes Argument dafür, in if-, while- und for-Anweisungen immer Klammern zu verwenden? Wenn Sie dies bewusst immer tun (wie zB bei MISRA-C vorgeschrieben), verschwindet das oben beschriebene Problem.

    – Steve Melnikoff

    2. April 2009 um 22:51 Uhr

  • Das Komma-Beispiel sollte sein #define BAR(X) (f(X), g(X)) Andernfalls kann die Operatorpriorität die Semantik durcheinander bringen.

    – Steward

    19. Mai 2011 um 12:58 Uhr

  • @DawidFerenczy: Obwohl du und ich vor viereinhalb Jahren einen guten Punkt machen, müssen wir in der realen Welt leben. Es sei denn, wir können garantieren, dass alle if Anweisungen usw. in unserem Code geschweifte Klammern verwenden, dann ist das Umschließen von Makros wie diesem eine einfache Möglichkeit, Probleme zu vermeiden.

    – Steve Melnikoff

    20. November 2013 um 17:16 Uhr

  • Beachten Sie das if(1) {...} else void(0) Form ist sicherer als die do {...} while(0) für Makros, deren Parameter Code sind, der in der Makroerweiterung enthalten ist, da er das Verhalten der Schlüsselwörter break oder Continue nicht ändert. Zum Beispiel: for (int i = 0; i < max; ++i) { MYMACRO( SomeFunc(i)==true, {break;} ) } verursacht unerwartetes Verhalten, wenn MYMACRO ist definiert als #define MYMACRO(X, CODE) do { if (X) { cout << #X << endl; {CODE}; } } while (0) da die Unterbrechung die While-Schleife des Makros und nicht die For-Schleife an der Makroaufrufstelle betrifft.

    – Chris Kline

    22. Juni 2015 um 16:23 Uhr


  • @As void(0) war ein Tippfehler, meinte ich (void)0. Und das glaube ich tut Lösen Sie das Problem “Dangling Else”: Beachten Sie, dass nach dem kein Semikolon steht (void)0. Ein baumelndes anderes in diesem Fall (zB if (cond) if (1) foo() else (void)0 else { /* dangling else body */ }) löst einen Kompilierungsfehler aus. Hier ist ein Live-Beispiel, das es demonstriert

    – Chris Kline

    27. Juli 2015 um 11:07 Uhr


Warum scheinbar bedeutungslose Do While und If Else Anweisungen in Makros verwenden
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.
}

  • Beachten Sie, dass das Ändern von Makros zur Inline-Funktion einige vordefinierte Standardmakros ändert, z. B. zeigt der folgende Code eine Änderung in FUNKTION und LINIE: #include #define Fmacro() printf(“%s %d\n”, FUNKTION, LINIE) inline void Finline() { printf(“%s %d\n”, FUNKTION, LINIE); } int main() { Fmacro(); Finline(); 0 zurückgeben; } (Fettgedruckte Begriffe sollten von doppelten Unterstrichen umschlossen werden – schlechter Formatierer!)

    – Gnubie

    23. August 2012 um 10:52 Uhr


  • Es gibt eine Reihe kleinerer, aber nicht völlig belangloser 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; der x in der Erweiterung sollte 32 sein. Eine komplexere Frage ist, was die Erweiterung ist MY_MACRO(i+7). Und ein weiterer ist die Erweiterung von MY_MACRO(0x07 << 6). Es gibt viel Gutes, aber es gibt auch ein paar ungepunktete i und nicht durchgestrichene ts.

    – Jonathan Leffler

    26. August 2013 um 4:17 Uhr

  • @Gnubie: Falls du noch hier bist und das noch nicht herausgefunden hast: Du kannst Sternchen und Unterstrichen in Kommentaren mit Backslashes entgehen, also wenn du tippst \_\_LINE\_\_ es wird als __LINE__ gerendert. IMHO ist es besser, nur Codeformatierung für Code zu verwenden; zum Beispiel, __LINE__ (was keine besondere Handhabung erfordert). PS Ich weiß nicht, ob das 2012 so war; Sie haben seitdem einige Verbesserungen am Motor vorgenommen.

    – Scott

    22. August 2017 um 23:07 Uhr


  • Ich weiß zu schätzen, dass mein Kommentar sechs Jahre zu spät kommt, aber die meisten C-Compiler sind nicht wirklich inline inline Funktionen (wie von der Norm zugelassen)

    – Andreas

    3. Januar 2019 um 6:01 Uhr

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

1647277217 515 Warum scheinbar bedeutungslose Do While und If Else Anweisungen in Makros verwenden
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");

  • Die Verwendung von Block-Clamping {} im Makro würde ausreichen, um den Makrocode zu bündeln, sodass alles für denselben Pfad mit if-Bedingung ausgeführt wird. Das do-while around wird verwendet, um ein Semikolon an Stellen zu erzwingen, an denen das Makro verwendet wird. somit wird das Makro erzwungen, dass es sich ähnlicher verhält. dies schließt die Anforderung für das nachgestellte Semikolon ein, wenn es verwendet wird.

    – Alexander Stöhr

    21. Dezember 2018 um 14:38 Uhr


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.

  • Die Verwendung von Block-Clamping {} im Makro würde ausreichen, um den Makrocode zu bündeln, sodass alles für denselben Pfad mit if-Bedingung ausgeführt wird. Das do-while around wird verwendet, um ein Semikolon an Stellen zu erzwingen, an denen das Makro verwendet wird. somit wird das Makro erzwungen, dass es sich ähnlicher verhält. dies schließt die Anforderung für das nachgestellte Semikolon ein, wenn es verwendet wird.

    – Alexander Stöhr

    21. Dezember 2018 um 14:38 Uhr


1647277218 632 Warum scheinbar bedeutungslose Do While und If Else Anweisungen in Makros 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 ifdie 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.

  • Dies ist zwar sehr clever, führt jedoch dazu, dass man mit Compiler-Warnungen über mögliche andere Dinge bombardiert wird.

    – Segmentiert

    26. März 2015 um 19:20 Uhr

  • Normalerweise verwenden Sie Makros, um eine abgeschlossene Umgebung zu erstellen, das heißt, Sie verwenden niemals eine break (oder continue) innerhalb eines Makros, um eine Schleife zu steuern, die außerhalb begann/endete, das ist einfach schlechter Stil und verbirgt potenzielle Austrittspunkte.

    – mirabilos

    21. Dezember 2016 um 11:51 Uhr

  • Es gibt auch eine Präprozessorbibliothek in Boost. Was ist daran umwerfend?

    – René

    1. Juni 2017 um 8:45 Uhr

  • Das Risiko mit else ((void)0) ist, dass jemand schreiben könnte YOUR_MACRO(), f(); und es wird syntaktisch gültig sein, aber niemals aufrufen f(). Mit do while es ist ein Syntaxfehler.

    – Melpomen

    7. Juni 2019 um 20:42 Uhr

  • @melpomene also was ist mit else do; while (0)?

    – Karl Lei

    15. August 2019 um 2:29 Uhr

1002470cookie-checkWarum scheinbar bedeutungslose Do-While- und If-Else-Anweisungen in Makros verwenden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy