In vielen C / C ++ - Makros sehe ich den Code des Makros in einer 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 sehen, was der do while
macht. Warum nicht einfach ohne schreiben?
#define FOO(X) f(X); g(X)
c++
c
c-preprocessor
c++-faq
jfm3
quelle
quelle
void
am Ende einen Ausdruck vom Typ hinzufügen ... wie ((void) 0) .do while
Konstrukt nicht mit return-Anweisungen kompatibel ist, sodass dasif (1) { ... } else ((void)0)
Konstrukt in Standard C besser kompatibel ist. In GNU C bevorzugen Sie das in meiner Antwort beschriebene Konstrukt.Antworten:
Die
do ... while
undif ... else
sind dazu da, damit ein Semikolon nach Ihrem Makro immer dasselbe bedeutet. Angenommen, Sie hatten so etwas wie Ihr zweites Makro.Wenn Sie nun
BAR(X);
eineif ... else
Anweisung verwenden, bei der die Textkörper der if-Anweisung nicht in geschweifte Klammern gesetzt sind, erhalten Sie eine schlechte Überraschung.Der obige Code würde sich erweitern in
Das ist syntaktisch falsch, da das else nicht mehr mit dem if verknüpft ist. Es hilft nicht, Dinge innerhalb des Makros in geschweifte Klammern zu setzen, da ein Semikolon nach den Klammern syntaktisch falsch ist.
Es gibt zwei Möglichkeiten, das Problem zu beheben. Das erste ist, ein Komma zu verwenden, um Anweisungen innerhalb des Makros zu sequenzieren, ohne es seiner Fähigkeit zu berauben, sich wie ein Ausdruck zu verhalten.
Die obige Version von bar
BAR
erweitert den obigen Code in das Folgende, was syntaktisch korrekt ist.Dies funktioniert nicht, wenn Sie stattdessen
f(X)
einen komplizierteren Code haben, der in einem eigenen Block abgelegt werden muss, beispielsweise um lokale Variablen zu deklarieren. Im allgemeinsten Fall besteht die Lösung darin,do ... while
das Makro als einzelne Anweisung zu verwenden, die ohne Verwirrung ein Semikolon enthält.Sie müssen nicht verwenden
do ... while
, Sie könnten auch etwas damit kochenif ... else
, obwohl es, wenn esif ... else
innerhalb eines erweitert wirdif ... else
, zu einem " baumelnden Anderen " führt, was es noch schwieriger machen könnte, ein bestehendes Problem des baumelnden Anderen zu finden, wie im folgenden Code .Es geht darum, das Semikolon in Kontexten zu verwenden, in denen ein baumelndes Semikolon fehlerhaft ist. Natürlich könnte (und sollte) an dieser Stelle argumentiert werden, dass es besser wäre,
BAR
als tatsächliche Funktion und nicht als Makro zu deklarieren .Zusammenfassend lässt sich sagen,
do ... while
dass dies dazu dient, die Mängel des C-Präprozessors zu umgehen. Wenn diese C-Styleguides Ihnen sagen, dass Sie den C-Präprozessor ausschalten sollen, ist dies die Art von Sache, über die sie sich Sorgen machen.quelle
#define BAR(X) (f(X), g(X))
anders lauten, da die Priorität des Operators die Semantik durcheinander bringen kann.if
Anweisungen usw. in unserem Code geschweifte Klammern verwenden, ist das Umschließen solcher Makros eine einfache Möglichkeit, Probleme zu vermeiden.if(1) {...} else void(0)
Formular ist sicherer als dasdo {...} while(0)
für Makros, deren Parameter Code sind, der in der Makroerweiterung enthalten ist, da es das Verhalten der Schlüsselwörter break oder continue nicht ändert. Beispiel:for (int i = 0; i < max; ++i) { MYMACRO( SomeFunc(i)==true, {break;} ) }
Verursacht unerwartetes Verhalten, wennMYMACRO
definiert wird,#define MYMACRO(X, CODE) do { if (X) { cout << #X << endl; {CODE}; } } while (0)
dass die Unterbrechung die while-Schleife des Makros und nicht die for-Schleife am Makroaufrufstandort beeinflusst.void(0)
war ein Tippfehler, meinte ich(void)0
. Und ich glaube , das ist die Lösung „dangling else“ Problem: Hinweis gibt , nachdem das kein Semikolon ist(void)0
. Ein anderes Baumeln in diesem Fall (z. B.if (cond) if (1) foo() else (void)0 else { /* dangling else body */ }
) löst einen Kompilierungsfehler aus. Hier ist ein Live-Beispiel, das es demonstriertMakros sind kopierte / eingefügte Textteile, die der Vorprozessor in den Originalcode einfügt. Der Autor des Makros hofft, dass der Ersatz einen gültigen Code erzeugt.
Es gibt drei gute "Tipps", um dies zu erreichen:
Helfen Sie dem Makro, sich wie echter Code zu verhalten
Normaler Code wird normalerweise durch ein Semikolon beendet. Sollte der Benutzer den Ansichtscode nicht benötigen ...
Dies 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 möglicherweise irgendwann durch eine echte Funktion ersetzen muss (möglicherweise inline). Das Makro sollte sich also wirklich wie eines verhalten.
Wir sollten also ein Makro haben, das ein Semikolon benötigt.
Erstellen 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 in einer if-Anweisung verwendet wird, ist dies problematisch:
Dieses Makro könnte erweitert werden als:
Die
g
Funktion wird unabhängig vom Wert von ausgeführtbIsOk
.Dies bedeutet, dass wir dem Makro einen Bereich hinzufügen müssen:
Erstellen Sie einen gültigen Code 2
Wenn das Makro so etwas wie:
Wir könnten ein anderes Problem im folgenden Code haben:
Weil es sich erweitern würde als:
Dieser Code wird natürlich nicht kompiliert. Die Lösung verwendet also wieder einen Bereich:
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:
Das do / while kann einen Bereich erstellen, wodurch der Code des Makros gekapselt wird, und benötigt am Ende ein Semikolon, wodurch es zu Code erweitert wird, der einen benötigt.
Der Bonus?
Der C ++ - Compiler optimiert die do / while-Schleife, da die Tatsache, dass ihre Nachbedingung falsch ist, zur Kompilierungszeit bekannt ist. Dies bedeutet, dass ein Makro wie folgt:
wird korrekt erweitert als
und wird dann als kompiliert und optimiert
quelle
void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; }
ist nicht die richtige Erweiterung; Dasx
in der Erweiterung sollte 32 sein. Ein komplexeres Problem ist, was die Erweiterung von istMY_MACRO(i+7)
. Und ein anderer ist die Erweiterung vonMY_MACRO(0x07 << 6)
. Es gibt eine Menge, die gut ist, aber es gibt einige ungepunktete Ichs und ungekreuzte T's.\_\_LINE\_\_
entkommen. Wenn Sie sie also eingeben, wird sie als __LINE__ gerendert. IMHO ist es besser, nur die Code-Formatierung für Code zu verwenden. Zum Beispiel__LINE__
(was keine besondere Behandlung erfordert). PS Ich weiß nicht, ob dies 2012 der Fall war. Seitdem haben sie einige Verbesserungen am Motor vorgenommen.inline
Funktionen (wie vom Standard zugelassen)@ jfm3 - Du hast eine schöne 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:
erweitert sich zu:
Dies ist syntaktisch korrekt, sodass kein Compilerfehler vorliegt, hat jedoch die wahrscheinlich unbeabsichtigte Folge, dass g () immer aufgerufen wird.
quelle
Die obigen Antworten erklären die Bedeutung dieser Konstrukte, aber es gibt einen signifikanten Unterschied zwischen den beiden, der nicht erwähnt wurde. In der Tat gibt es einen Grund, das
do ... while
demif ... else
Konstrukt vorzuziehen .Das Problem des
if ... else
Konstrukts ist, dass Sie nicht gezwungen werden , das Semikolon einzufügen. Wie in diesem Code:Obwohl wir das Semikolon (aus Versehen) weggelassen haben, wird der Code auf erweitert
und wird stillschweigend kompiliert (obwohl einige Compiler möglicherweise eine Warnung für nicht erreichbaren Code ausgeben). Die
printf
Anweisung wird jedoch niemals ausgeführt.do ... while
Konstrukt hat kein solches Problem, da das einzige gültige Token nach demwhile(0)
ein Semikolon ist.quelle
FOO(1),x++;
was uns wieder ein falsches Positiv gibt. Einfach benutzendo ... while
und fertig.do ... while (0)
vorzuziehen ist, aber es hat einen Nachteil: Abreak
odercontinue
steuert diedo ... while (0)
Schleife, nicht die Schleife, die den Makroaufruf enthält. Derif
Trick hat also immer noch Wert.break
oder ein platzieren könntencontinue
, das sich in Ihrer Makro-do {...} while(0)
Pseudo-Schleife befindet. Selbst im Makroparameter würde es einen Syntaxfehler geben.do { ... } while(0)
anstelle desif whatever
Konstrukts ist die idiomatische Natur. Dasdo {...} while(0)
Konstrukt ist weit verbreitet, bekannt und wird von vielen Programmierern häufig verwendet. Ihre Begründung und Dokumentation ist leicht bekannt. Nicht so beimif
Konstrukt. Es ist daher weniger mühsam, bei der Codeüberprüfung zu groken.#define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0
. Es könnte wie folgt verwendet werdenCHECK(system("foo"), break;);
, wenn sich dasbreak;
auf die Schleife beziehen soll, die denCHECK()
Aufruf einschließt .Während erwartet wird, dass Compiler die
do { ... } while(false);
Schleifen optimieren , gibt es eine andere Lösung, die dieses Konstrukt nicht erfordern würde. Die Lösung besteht darin, den Komma-Operator zu verwenden:oder noch exotischer:
Dies funktioniert zwar gut mit separaten Anweisungen, funktioniert jedoch nicht in Fällen, in denen Variablen erstellt und als Teil von
#define
:Damit wäre man gezwungen, das do / while-Konstrukt zu verwenden.
quelle
Peterson's Algorithm
.Die P99-Präprozessorbibliothek von Jens Gustedt (ja, die Tatsache, dass so etwas existiert, hat mich auch umgehauen!) Verbessert das
if(1) { ... } else
Konstrukt auf eine kleine, aber signifikante Weise, indem Folgendes definiert wird:Der Grund dafür ist, dass im Gegensatz zum
do { ... } while(0)
Konstruktbreak
undcontinue
innerhalb des angegebenen Blocks immer noch gearbeitet wird, jedoch((void)0)
ein Syntaxfehler erstellt wird, wenn das Semikolon nach dem Makroaufruf weggelassen wird, wodurch der nächste Block sonst übersprungen würde. (Es gibt hier eigentlich kein "Dangling else" -Problem, da daselse
an das nächste bindetif
, das das im Makro ist.)Wenn Sie an den Dingen interessiert sind, die mit dem C-Präprozessor mehr oder weniger sicher ausgeführt werden können, lesen Sie diese Bibliothek.
quelle
break
(odercontinue
) innerhalb eines Makros, um eine Schleife zu steuern, die außerhalb begann / endete. Das ist nur ein schlechter Stil und verbirgt potenzielle Austrittspunkte.else ((void)0)
besteht darin, dass jemand schreibtYOUR_MACRO(), f();
und es syntaktisch gültig ist, aber niemals anruftf()
. Mit istdo
while
es ein Syntaxfehler.else do; while (0)
?Aus bestimmten Gründen kann ich die erste Antwort nicht kommentieren ...
Einige von Ihnen zeigten Makros mit lokalen Variablen, aber niemand erwähnte, dass Sie nicht einfach einen Namen in einem Makro verwenden können! Es wird den Benutzer eines Tages beißen! Warum? Weil die Eingabeargumente in Ihre Makrovorlage eingesetzt werden. Und in Ihren Makrobeispielen verwenden Sie den wahrscheinlich am häufigsten verwendeten variablen Namen i .
Zum Beispiel beim folgenden Makro
wird in der folgenden Funktion verwendet
Das Makro verwendet nicht die beabsichtigte Variable i, die am Anfang von some_func deklariert ist, sondern die lokale Variable, die in der do ... while-Schleife des Makros deklariert ist.
Verwenden Sie daher niemals gebräuchliche Variablennamen in einem Makro!
quelle
int __i;
.mylib_internal___i
. B. oder ähnliches.Erläuterung
do {} while (0)
undif (1) {} else
sollen sicherstellen, dass das Makro auf nur 1 Befehl erweitert wird. Andernfalls:würde erweitern zu:
Und
g(X)
würde außerhalb derif
Steueranweisung ausgeführt werden. Dies wird bei der Verwendung vondo {} while (0)
und vermiedenif (1) {} else
.Bessere Alternative
Mit einem GNU- Anweisungsausdruck (der nicht Teil von Standard C ist) haben Sie eine bessere Möglichkeit als
do {} while (0)
und können diesif (1) {} else
lösen, indem Sie einfach Folgendes verwenden({})
:Und diese Syntax ist kompatibel mit Rückgabewerten (beachten Sie, dass dies
do {} while (0)
nicht der Fall ist), wie in:quelle
Ich glaube nicht, dass es erwähnt wurde, also denken Sie darüber nach
würde übersetzt werden in
Beachten Sie, wie
i++
das Makro zweimal auswertet. Dies kann zu interessanten Fehlern führen.quelle
do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)