Warum scheinbar bedeutungslos do-while-und if-else-Anweisungen in Makros?
In vielen C/C++ - Makros sehe ich den code des Makros, eingehüllt in das, was scheint wie eine sinnlose 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 die do while
tut. Warum nicht einfach schreiben das ohne es?
#define FOO(X) f(X); g(X)
Für das Beispiel mit der anderen, ich möchte hinzufügen, ein Ausdruck der
Erinnerung daran, dass die
void
Typ am Ende... wie ((void)0).Erinnerung daran, dass die
do while
Konstrukt nicht kompatibel mit return-Anweisungen, so dass die if (1) { ... } else ((void)0)
Konstrukt hat mehr kompatible Nutzungen in Standard-C. Und in GNU-C, Sie bevorzugen das Konstrukt beschrieben in meiner Antwort.
InformationsquelleAutor jfm3 | 2008-09-30
Du musst angemeldet sein, um einen Kommentar abzugeben.
Den
do ... while
undif ... else
gibt es so zu machen, dass eineSemikolon nach Ihrem makro bedeutet immer das gleiche. Lassen Sie uns sagen Sie, dass Sie
hatte so etwas wie das zweite makro.
Nun, wenn Sie waren zu verwenden
BAR(X);
in einemif ... else
Anweisung, wo die Körper der if-Anweisung wurden nicht gewickelt, in geschweiften Klammern, würden Sie eine böse überraschung.Den obigen code erweitern würden in
ist syntaktisch falsch, da die sonst keine Verbindung mehr mit dem wenn. Es hilft nicht, wickeln Sie Dinge in geschweiften Klammern innerhalb der makro -, weil ein Semikolon nach der geschweiften Klammern ist syntaktisch falsch.
Gibt es zwei Möglichkeiten, das problem zu lösen. Die erste ist, verwenden Sie ein Komma, um die Sequenz von Anweisungen in das makro, ohne zu berauben Sie Ihrer Fähigkeit, zu handeln wie ein Ausdruck.
Der oben genannten version der bar
BAR
erweitert den obigen code in das, was folgt, das ist syntaktisch korrekt.Das funktioniert nicht, wenn anstelle von
f(X)
Sie haben eine komplizierte Körper-code, der gehen muss, im eigenen block, sagen wir zum Beispiel, lokale Variablen deklarieren. Im allgemeinsten Fall die Lösung ist, etwas zu verwenden, wiedo ... while
zu verursachen, dass das makro aus einer einzigen Anweisung, die ein Semikolon ohne Verwirrung.Müssen Sie nicht verwenden
do ... while
Sie konnte Kochen, etwas mitif ... else
so gut, obwohl, wennif ... else
erweitert, die in einerif ... else
es führt zu einem "dangling else", um einen vorhandenen dangling else-problem noch schwieriger zu finden, als in den folgenden code.Der Punkt ist, verwenden Sie das Semikolon in Kontexten, in denen ein herabhängendes Semikolon ist falsch. Natürlich, es könnte (und wahrscheinlich sollte) argumentiert an dieser Stelle, dass es besser wäre, zu erklären
BAR
als eine tatsächliche Funktion, nicht ein makro.In der Zusammenfassung, die
do ... while
ist es zu arbeiten, um die Mängel der C-Präprozessor. Bei den C-Stil-Führer sagen, Sie lag vor der C-Präprozessor, dies ist die Art von Sache, die Sie besorgt sind.Ist das nicht ein starkes argument, immer geschweifte Klammern in if -, while-und for-Anweisungen? Wenn Sie sind stets bemüht, dies zu tun (wie es nötig ist, für MISRA-C z.B.), das oben beschriebene problem geht Weg.
Warum ist es, dass ich fühle mich schlechter als ein Programmierer nach dem lernen all dies...
Das Komma Beispiel sollte
#define BAR(X) (f(X), g(X))
sonst Rangfolge kann mess up die Semantik.obwohl beide-Sie und mich-aus-vier-und-ein-halb-Jahre-Woche machen einen guten Punkt, wir müssen in der realen Welt Leben. Es sei denn, wir können garantieren, dass alle
if
- Anweisungen, etc., in unserem code verwenden Sie geschweifte Klammern, dann wickeln Makros wie dieses ist eine einfache Weise des Vermeidens von Problemen.InformationsquelleAutor jfm3
Makros copy/Paste text-Stücke der pre-Prozessor setzen wird, in der echte code; das makro ist Autor hofft, dass die Ersatz Gültiger code.
Gibt es drei gute "Tipps" gelingt:
Helfen, das makro Verhalten sich wie echte-code
Normale code ist in der Regel beendet durch ein Semikolon. Sollte der Nutzer anzeigen-code nicht benötigen eine...
Dies bedeutet, dass der Benutzer erwartet, dass der compiler einen Fehler erzeugen, wenn das Semikolon fehlt.
Aber die wirkliche echte gute Grund ist, dass irgendwann, das makro ist der Autor vielleicht brauchen Sie den makro-mit echter Funktion (vielleicht inlined). So sollte das makro wirklich Verhalten sich wie einer.
Also, wir haben ein makro benötigen, semi-colon.
Ein Gültiger code
Wie gezeigt, in jfm3 Antwort, manchmal ist das makro enthält mehr als eine Anweisung. Und wenn das makro verwendet wird, innerhalb einer if-Anweisung, wird dies problematisch:
Dieses makro erweitert werden könnte als:
Den
g
- Funktion wird ausgeführt, unabhängig vom Wert derbIsOk
.Dies bedeutet, dass wir haben müssen, um fügen Sie einen Rahmen um das makro:
Ein Gültiger code 2
Wenn das makro so etwas wie:
Hätten wir ein weiteres problem in dem folgenden code:
Da würde es erweitern, wie:
Dieser code wird nicht kompiliert, natürlich. Also, noch einmal, die Lösung ist mit einem scope:
Den code verhält sich korrekt wieder.
Die Kombination von semi-colon + scope-Effekte?
Es ist eine C/C++ idiom, das führt zu diesem Effekt: Die do/while-Schleife:
Die do/while können einen Bereich erstellen, damit die Kapselung der makro-code, und muss ein Semikolon am Ende, so die Expansion in code benötigen eine.
Den bonus?
Den C++ - compiler optimieren wird, entfernt die do/while-Schleife, als die Tatsache, dass seine post-Bedingung false ist zur Kompilierzeit bekannt ist. Dies bedeutet, dass ein makro wie:
erweitern wird korrekt als
und wird dann kompiliert und optimiert Sie Weg als
Es gibt eine Reihe von kleineren, aber nicht völlig belanglos Fragen mit dieser Antwort. Zum Beispiel:
void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; }
ist nicht die richtige expansion;x
in den ausbau sollte 32. Eine komplexere Frage ist, was ist die Ausweitung derMY_MACRO(i+7)
. Und ein weiterer ist der ausbau derMY_MACRO(0x07 << 6)
. Es gibt viel, das ist gut, aber es gibt einige undotted ich und löste t ist.Ich Falle, du bist immer noch hier, und Sie haben nicht erkannt hat: Sie kann entkommen, Sternchen und Unterstriche in den Kommentaren mit backslashes, so dass, wenn Sie geben
\_\_LINE\_\_
macht es wie __LINE__. IMHO ist es besser, einfach zu bedienen code-Formatierung für code; zum Beispiel__LINE__
(man braucht keine spezielle Behandlung). P. S. ich weiß nicht, ob dies wahr war im Jahr 2012; Sie haben durchaus ein paar Verbesserungen an der engine da dann.Zu verstehen, dass mein Kommentar ist sechs Jahre zu spät, aber die meisten C-Compilern nicht wirklich inline
inline
Funktionen (wie von der Norm zugelassene)InformationsquelleAutor paercebal
@jfm3 - Sie haben eine schöne Antwort auf die Frage. Vielleicht möchten Sie auch hinzufügen, dass die makro-idiom verhindert auch die möglicherweise noch gefährlicher (weil es keine Fehler) unerwünschte Verhalten mit einfachen "wenn" - Aussagen:
erweitert:
ist syntaktisch korrekt, so gibt es keine compiler-Fehler, aber hat der wohl nicht beabsichtigte Folge, dass g() wird immer aufgerufen werden.
Oder Sie kann gegeben werden, eine Erhöhung, wenn Sie arbeiten für eine drei-Buchstaben-Agentur und heimlich einsetzen, dass code in einer weit verbreiteten open-source-Programm... 🙂
Und dieser Kommentar fällt mir nur der goto-fail-Linie in der jüngsten SSL-cert-Verifizierung Fehler in Apple-Betriebssystemen
InformationsquelleAutor
Den oben genannten Antworten, die erklären, die Bedeutung dieser Konstrukte, aber es gibt einen signifikanten Unterschied zwischen den beiden, das wurde nicht erwähnt. In der Tat, es gibt einen Grund zu bevorzugen, die
do ... while
zu denif ... else
konstruieren.Das problem der
if ... else
Konstrukt ist, dass es nicht Kraft das Semikolon. Wie in diesem code:Obwohl wir ausgelassen das Semikolon (durch Fehler), wird der code zu erweitern, um
und wird automatisch kompilieren (obwohl einige Compiler kann die Ausgabe einer Warnung bei unreachable code). Aber die
printf
- Anweisung wird nie ausgeführt werden.do ... while
Konstrukt nicht ein solches problem, da die einzige gültige token nach derwhile(0)
ist ein Semikolon.FOO(1),x++;
die wieder geben uns ein false positive ist. Verwenden Sie einfachdo ... while
- und das ist es.Dokumentieren Sie das makro zu vermeiden, die Missverständnisse sollten ausreichen. Ich willige ein, dass
do ... while (0)
ist vorzuziehen, aber es hat einen Nachteil: Einebreak
odercontinue
wird die Kontrolle derdo ... while (0)
Schleife, nicht die Schleife mit dem macro-Aufruf. Also dieif
trick hat immer noch Wert.Ich sehe nicht, wo Sie könnten ein
break
oder einecontinue
würde gesehen werden, wie innerhalb Ihrer Makrosdo {...} while(0)
pseudo-Schleife. Auch in der makro-parameter wäre es ein syntax-Fehler.Ein weiterer Grund für die Verwendung
do { ... } while(0)
stattif whatever
Konstrukt, das idiomatischer Natur. Diedo {...} while(0)
konstruieren ist weit verbreitet, bekannt und verwendet eine Menge von vielen Programmierern. Seine Begründung und Dokumentation ist ohne weiteres bekannt. Nicht so für denif
konstruieren. Es nimmt daher weniger Aufwand zu verstehen, wenn dabei code-review.Ich habe Menschen gesehen, die Makros schreiben, nehmen Sie code-Blöcke als Argumente (die ich nicht unbedingt empfehlen). Zum Beispiel:
#define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0
. Es könnte verwendet werden, wieCHECK(system("foo"), break;);
, wo diebreak;
bezieht sich auf die Schleife umschließt dieCHECK()
Aufruf.InformationsquelleAutor ybungalobill
Während es wird erwartet, dass der Compiler die optimieren der Weg der
do { ... } while(false);
Schleifen, gibt es eine andere Lösung, die nicht erfordern, dass konstruieren. Die Lösung ist die Verwendung des Komma-operators:oder sogar mehr exotisch:
Während dieser arbeiten wird auch mit separaten Anweisungen, es funktioniert nicht mit Fällen, in denen Variablen aufgebaut sind und als Teil der
#define
:Mit diesem würde man gezwungen sein, mit einer do/while-Konstrukt.
danke, da der Komma-operator garantiert nicht die Ausführung, um diese Verschachtelung ist ein Weg, um zu erzwingen, dass.
False; der Komma-operator ist ein sequence point ist und dabei nicht garantieren die Ausführung bestellen. Ich vermute, Sie verwechseln es mit dem Komma in Funktion-argument-Listen.
Die zweite exotischen Vorschlag machte meinen Tag.
Wollte nur hinzufügen, dass der Compiler gezwungen ist, zu bewahren, das Programm beobachtbare Verhalten so optimieren die do/while away ist nicht viel von eine große Sache ist (vorausgesetzt der compiler Optimierungen sind korrekt).
InformationsquelleAutor Marius
Jens Gustedt ist P99 preprocessor-Bibliothek (ja, die Tatsache, dass so etwas existiert, hat mich wirklich umgehauen!) verbessert die
if(1) { ... } else
Konstrukt in eine kleine, aber deutliche Weise durch die Definition der folgenden:Die Gründe für dieses ist, dass, im Gegensatz zu den
do { ... } while(0)
Konstruktbreak
undcontinue
noch die Arbeit im inneren des Blocks, aber die((void)0)
erzeugt einen syntax-Fehler, wenn das Semikolon weggelassen wird, nachdem das makro aufrufen, die ansonsten überspringen Sie den nächsten block. (Es ist nicht wirklich ein "dangling else" - problem auch hier, da dieelse
bindet an die nächsteif
, das ist die, die im makro.)Wenn Sie daran interessiert sind, die Art von Dingen, die getan werden kann, mehr oder weniger sicher mit dem C-Präprozessor, check-out, Bibliothek.
Sie in der Regel verwenden Sie Makros erstellen, eine geschlossene Umgebung ist, die Sie nie verwenden, ein
break
(odercontinue
) innerhalb eines Makros zum Steuern einer Schleife, die begann/endete draußen, das ist einfach schlechter Stil, und blendet mögliche exit-Punkte.Es gibt auch eine preprocessor-Bibliothek, die in Boost. Was ist mind-blowing darüber?
InformationsquelleAutor Isaac Schwabacher
Für einige Gründe, warum ich kann nicht Kommentar auf die erste Antwort...
Einige von Ihnen zeigten Makros mit lokalen Variablen, aber niemand erwähnt, dass man nicht einfach jeden beliebigen Namen verwenden in einem makro! Es beißen die Benutzer einige Tage! Warum? Da die input-Argumente ersetzt werden, die in Ihrem makro-Vorlage. Und in Ihrem makro Beispiele, die Sie haben, verwenden Sie die wohl am häufigsten verwendete variabled Namen ich.
Zum Beispiel wenn das folgende makro
wird in der folgenden Funktion
das makro wird nicht mit der soll-variable i deklariert ist am Anfang some_func, aber die lokale variable deklariert ist, in der do ... while-Schleife des Makros.
So, nie mit gemeinsamen Variablen-Namen in einem makro!
int __i;
.Das ist eigentlich falsch und illegal C; führende Unterstriche sind reserviert für die Verwendung durch die Umsetzung. Der Grund, warum Sie habe das "übliche Muster" ist aus Lesen system Header ("Umsetzung"), die müssen sich darauf beschränken, diesen reservierten Namensraum. Für Anwendungen/Bibliotheken, die Sie auswählen sollten, Ihre eigenen obskuren, die unwahrscheinlich, zu kollidieren Namen ohne Unterstriche, z.B.
mylib_internal___i
oder ähnliches.Du hast Recht - ich habe eigentlich gelesen das in einer "Anwendung", der Linux-kernel, aber es ist eine Ausnahme, da es verwendet keine standard-Bibliothek (technisch, eine "freistehende" C-Implementierung statt eines "hosted").
das ist nicht ganz korrekt: führende Unterstriche , gefolgt von einer Kapital-oder zweiten Unterstrich sind reserviert für die Umsetzung in allen Kontexten. Führende Unterstriche, gefolgt von etwas, das noch nicht reserviert, im lokalen Bereich.
Ja, aber die Unterscheidung ist ausreichend subtil, dass am besten finde ich es, Menschen zu erzählen, genau dies nicht zu verwenden, solche Namen überhaupt. Die Leute, die verstehen, die Feinheiten vermutlich schon weiß, dass ich mich gern über die details. 🙂
InformationsquelleAutor Mike Meyer
Ich glaube nicht, dass es erwähnt wurde, so betrachten Sie dieses
übersetzt werden würde, in
bemerken, wie
i++
ausgewertet wird zweimal durch das makro. Dies kann dazu führen, dass einige interessante Fehler.Wahr. Aber relevant für das Thema Makros vs. Funktionen und wie man ein makro schreiben, das verhält sich wie eine Funktion...
In ähnlicher Weise zu der obigen das ist keine Antwort sondern ein Kommentar. On topic: das ist, warum Sie alles verwenden, nur einmal:
do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)
InformationsquelleAutor John Nilsson
Erklärung
do {} while (0)
undif (1) {} else
sind, um sicherzustellen, dass das makro erweitert werden, um nur 1 Anleitung. Ansonsten:erweitern würde:
Sowie
g(X)
ausgeführt werden würde außerhalb derif
control-Anweisung. Dies wird vermieden, wenn mitdo {} while (0)
undif (1) {} else
.Bessere alternative
Mit einem GNU Anweisung Ausdruck (nicht Teil von standard-C), Sie haben einen besseren Weg, als
do {} while (0)
undif (1) {} else
um dieses Problem zu lösen, indem Sie einfach({})
:Und diese syntax ist kompatibel mit Werte zurückgeben (beachten Sie, dass
do {} while (0)
ist das nicht), wie in:InformationsquelleAutor Cœur
Ich gefunden habe, ist dieser trick sehr hilfreich ist in Situationen, in denen Sie sequenziell verarbeiten, einen bestimmten Wert. Auf jeder Ebene der Verarbeitung, wenn einige Fehler oder unwirksamen Bedingung tritt auf, können Sie vermeiden, die weitere Verarbeitung und brechen früh auf. z.B.
Ich hätte lieber
if (process_first() && process_second() && process_third()) { // do whatever, all success }
und die Freiheit haben, benutzen Sie die Funktion Argumente? Dies gibt auch Spielraum für andere Rückgabewerte auf Erfolg (wie>= 0
) ohne die Notwendigkeit, erstellen Sie ein weiteres makro.Dies ist auch nicht eine wirkliche Antwort auf die Frage...
das wird nicht funktionieren, auch wenn Sie mit der if-else-Sequenzen und setzen so ein makro in den if-block...
InformationsquelleAutor pankaj
Den Grund
do {} while (0)
überif (1) {}
ist, dass es keine Möglichkeit gibt, kann mir jemand den code ändern, bevor Sie das makrodo {} while (0)
werden einige andere Art von block. Zum Beispiel, wenn Sie aufgerufen, ein makro, umgeben vonif (1) {}
wie:Das ist eigentlich eine
else if
. Feine UnterschiedInformationsquelleAutor ericcurtin