Frage

In vielen C / C ++ Makros Ich sehe den Code des Makros eingewickelt in, was scheint wie eine sinnlose do while Schleife. Hier sind einige 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 der do while tut. Warum nicht einfach das schreiben, ohne es?

#define FOO(X) f(X); g(X)
War es hilfreich?

Lösung

Die do ... while und if ... else gibt es zu machen, so dass ein Semikolon nach dem Makro bedeutet immer das Gleiche. Angenommen, Sie hatte so etwas wie Ihre zweite Makro.

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

Nun, wenn Sie BAR(X); in einer if ... else Anweisung verwenden sind, wo der Körper der if-Anweisung nicht in geschweifte Klammern eingewickelt wurde, würden Sie eine schlechte Überraschung.

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

Der obige Code würde erweitern in

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

, die syntaktisch nicht korrekt ist, da die sonst nicht mehr mit dem, wenn zugeordnet ist. Es hilft nicht, innerhalb der Makro-Dinge in geschweiften Klammern zu wickeln, da ein Semikolon nach den Klammern syntaktisch nicht korrekt ist.

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

Es gibt zwei Möglichkeiten, das Problem zu lösen. Die erste ist ein Komma Sequenz Aussagen im Makro zu verwenden, ohne es seine Fähigkeit zu berauben wie ein Ausdruck zu handeln.

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

Die obige Version bar BAR erweitert den obigen Code in dem, was folgt, die syntaktisch korrekt ist.

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

Das funktioniert nicht, wenn statt f(X) Sie einen komplizierteren Körper von Code, der in einem eigenen Block gehen muss, sagen sie zum Beispiel lokale Variablen zu deklarieren. Im allgemeinsten Fall ist die Lösung so etwas wie do ... while zu verwenden, um das Makro zu verursachen eine einzelne Anweisung zu sein, die ein Semikolon ohne Verwirrung führt.

#define BAR(X) do { \
  int i = f(X); \
  if (i > 4) g(i); \
} while (0)

Sie müssen do ... while nicht verwenden, könnten Sie etwas mit if ... else auch kochen, obwohl, wenn if ... else innerhalb eines if ... else erweitert es zu einem führt „ baumelnden sonst ", die ein bestehendes baumeln sonst Problem machen könnte noch schwerer zu finden, wie in dem folgenden Code.

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

Der Punkt ist das Semikolon in Zusammenhängen zu verwenden, wo ein Semikolon baumelnden fehlerhaft ist. Natürlich könnte es (und wahrscheinlich soll) an dieser Stelle argumentieren, dass es besser wäre, BAR als eine tatsächliche Funktion, keinen Makro zu erklären.

Insgesamt ist die do ... while es um die Mängel des C-Präprozessor zu arbeiten. Wenn dieser C-Styleguides Ihnen sagt, das C-Präprozessor zu entlassen, das ist die Art von Sache, die sie über sie Sorgen machen.

Andere Tipps

Makros sind Kopieren / Einfügen Textstücke der Vorprozessor im echten Code gesetzt wird; der Autor Makro hofft, dass der Ersatz gültigen Code produzieren.

Es gibt drei gute „Tipps“, dass um erfolgreich zu sein:

Hilfe

das Makro verhalten sich wie echte Code

Normalcode wird in der Regel durch ein Semikolon beendet. Sollte der Benutzer Ansicht Code nicht ein benötigen ...

doSomething(1) ;
DO_SOMETHING_ELSE(2)  // <== Hey? What's this?
doSomethingElseAgain(3) ;

Dies bedeutet, dass der Benutzer der Compiler erwartet einen Fehler zu erzeugen, wenn das Semikolon fehlt.

Aber der eigentliche wirklich gute Grund dafür ist, irgendwann einmal, dass die Autor der Makro wird vielleicht müssen Sie das Makro mit einer echten Funktion ersetzen (vielleicht inlined). So sollte das Makro wirklich verhalten sich wie ein.

So wir ein Makro benötigen Semikolon haben sollte.

einen gültigen Code

Wie in jfm3 Antwort gezeigt, manchmal das Makro enthält mehr als einen Befehl. Und wenn sich das Makro in einer if-Anweisung verwendet wird, wird dies problematisch:

if(bIsOk)
   MY_MACRO(42) ;

Dieses Makro erweitert werden könnte, wie:

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

if(bIsOk)
   f(42) ; g(42) ; // was MY_MACRO(42) ;

Die g Funktion wird unabhängig vom Wert von bIsOk ausgeführt werden.

Das bedeutet, dass wir einen Rahmen zum Makro hinzufügen müssen haben:

#define MY_MACRO(x) { f(x) ; g(x) ; }

if(bIsOk)
   { f(42) ; g(42) ; } ; // was MY_MACRO(42) ;

einen gültigen Code 2

Wenn das Makro ist so etwas wie:

#define MY_MACRO(x) int i = x + 1 ; f(i) ;

Wir konnten ein weiteres Problem in dem folgenden Code haben:

void doSomething()
{
    int i = 25 ;
    MY_MACRO(32) ;
}

Weil es würde erweitern, wie:

void doSomething()
{
    int i = 25 ;
    int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}

Dieser Code wird nicht kompiliert, natürlich. Also, noch einmal, die Lösung wird mit einem Umfang:

#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 korrekt wieder.

Die Kombination von Semikolon + Umfang Effekte?

Es ist ein C / C ++ Idiom, das diesen Effekt erzeugt: Die do / while-Schleife:

do
{
    // code
}
while(false) ;

Die do / while kann einen Rahmen schaffen, damit der Code des Makro-Einkapselung und muss ein Semikolon am Ende, also in den Code erweitert ein benötigen.

Der Bonus?

Die C ++ Compiler optimieren die do away / while-Schleife, wie die Tatsache, seine post-Bedingung falsch ist, zum Zeitpunkt der Kompilierung bekannt. 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.
}

erweitern wird korrekt 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 als

zusammengestellt und wegoptimiert
void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
   {
      f(43) ; g(43) ;
   }

   // Etc.
}

@ jfm3 - Sie haben eine schöne Antwort auf die Frage. Sie könnten auch hinzufügen mögen, dass das Makro-Idiom verhindert auch die möglicherweise gefährlicher (weil es kein Fehler) unbeabsichtigtes Verhalten mit einfachen ‚wenn‘ Aussagen:

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

if (test) FOO( baz);

erweitert zu:

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

, die syntaktisch korrekt ist, so gibt es keinen Compiler-Fehler ist, hat aber die vermutlich unbeabsichtigte Folge, dass g () wird immer dann aufgerufen werden.

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

Das Problem des if ... else Konstrukt ist, dass es nicht Kraft Sie das Semikolon setzen. Wie in diesem Code:

FOO(1)
printf("abc");

Obwohl wir das Semikolon weggelassen (versehentlich), dehnt sich der Code

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

und wird stillschweigend kompilieren (obwohl einige Compiler eine Warnung für nicht erreichbar Code ausgeben kann). Aber die printf Anweisung nie ausgeführt werden.

do ... while Konstrukt nicht ein solches Problem haben, da die einzige gültige Token nach dem while(0) ein Semikolon ist.

Während es wird erwartet, dass Compiler die do { ... } while(false); Schleifen optimiert entfernt, es eine andere Lösung, die nicht das Konstrukt erfordern würde. Die Lösung ist, den Komma-Operator zu verwenden:

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

oder sogar mehr exotically:

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

Während dies gut mit separaten Anweisungen arbeitet, wird es nicht mit Fällen arbeiten, wo Variablen als Teil des #define konstruiert und verwendet:

#define FOO(X) (int s=5,f((X)+s),g((X)+s))

Mit diesem gezwungen würde die do / while-Konstrukt zu verwenden.

Jens Gustedt P99 Präprozessor Bibliothek (ja, die Tatsache, dass es so etwas gibt blies meine etwas dagegen zu) verbessert die if(1) { ... } else Konstrukt in einer kleinen, aber signifikanten Art und Weise durch die Definition der folgenden:

#define P99_NOP ((void)0)
#define P99_PREFER(...) if (1) { __VA_ARGS__ } else
#define P99_BLOCK(...) P99_PREFER(__VA_ARGS__) P99_NOP

Der Grund dafür ist, dass, im Gegensatz zu dem do { ... } while(0) Konstrukt, break und continue noch innerhalb des gegebenen Blockes arbeiten, aber die ((void)0) erzeugt einen Syntaxfehler, wenn das Semikolon nach dem Makroaufruf weggelassen wird, die den nächsten Block sonst überspringen würde. (Es ist nicht wirklich ein „baumelnde sonst“ hier Problem, da die else zum nächstgelegenen if binden, die die in der Makro ist.)

Wenn Sie in den möglichen Dinge interessieren, die mehr oder weniger sicher mit dem C-Präprozessor getan werden kann, überprüfen Sie die Bibliothek aus.

Aus irgendwelchen Gründen kann ich nicht auf der ersten Antwort Kommentar ...

Einige von Ihnen zeigte Makros mit lokalen Variablen, aber niemand erwähnt, dass Sie nicht nur einen beliebigen Namen in einem Makro verwenden können! Es wird dem Benutzer eines Tages beißen! Warum? Da die Eingabeargumente in der Makrovorlage ersetzt. Und in Ihren Makro Beispielen verwenden Sie haben die wohl am häufigsten verwendeten variabled Namen i .

Zum Beispiel, wenn das folgende Makro

#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)

wird in der folgenden Funktion

void some_func(void) {
    int i;
    for (i = 0; i < 10; ++i)
        FOO(i);
}

das Makro nicht die beabsichtigte Variable i, dass zu Beginn des some_func erklärt wird, aber die lokale Variable, die in der nicht erklärt wird ... while-Schleife des Makros.

So verwenden nie gemeinsamen Variablennamen in einem Makro!

Erklärung

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

if (something)
  FOO(X); 

würde erweitern:

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

Und g(X) würde außerhalb der if Steueranweisung ausgeführt werden. Dies wird vermieden, wenn do {} while (0) und if (1) {} else verwendet wird.


Bessere Alternative

Mit einer GNU Anweisung Ausdruck (nicht Teil des Standard-C), haben Sie eine bessere Art und Weise als do {} while (0) und if (1) {} else dieses Problem zu lösen, indem man einfach ({}) mit:

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

Und diese Syntax ist kompatibel mit Rückgabewerten (beachten Sie, dass do {} while (0) nicht ist), wie in:

return FOO("X");

Ich glaube nicht, es war dies so betrachten erwähnt

while(i<100)
  FOO(i++);

würde in

übersetzt werden
while(i<100)
  do { f(i++); g(i++); } while (0)

Beachten Sie, wie i++ durch das Makro zweimal ausgewertet wird. Dies kann zu einigen interessanten Fehlern führen.

ich gefunden habe, dieser Trick ist sehr hilfreich in Situationen, in denen Sie Prozess einen bestimmten Wert, um sequentiell haben. Auf jeder Ebene der Verarbeitung, wenn ein Fehler oder ungültige Bedingung auftritt, können Sie die weitere Verarbeitung vermeiden und früh ausbrechen. z.

#define CALL_AND_RETURN(x)  if ( x() == false) break;
do {
     CALL_AND_RETURN(process_first);
     CALL_AND_RETURN(process_second);
     CALL_AND_RETURN(process_third);
     //(simply add other calls here)
} while (0);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top