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.

17voto

Marius Punkte 3250

Es wird zwar erwartet, dass Compiler die do { ... } while(false); Schleifen, gibt es eine andere Lösung, die dieses Konstrukt nicht erfordert. Die Lösung besteht darin, den Komma-Operator zu verwenden:

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

oder noch exotischer:

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

Während dies bei separaten Anweisungen gut funktioniert, funktioniert es nicht in Fällen, in denen Variablen konstruiert und als Teil der #define :

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

Damit wäre man gezwungen, das do/while-Konstrukt zu verwenden.

0 Stimmen

Danke, da der Komma-Operator keine Ausführungsreihenfolge garantiert, ist diese Verschachtelung eine Möglichkeit, dies zu erzwingen.

17 Stimmen

@Marius: Falsch; der Kommaoperator ist ein Sequenzpunkt und damit fait die Reihenfolge der Ausführung garantieren. Ich vermute, Sie haben es mit dem Komma in Funktionsargumentlisten verwechselt.

0 Stimmen

Wollte nur hinzufügen, dass Compiler gezwungen sind, das Programm beobachtbare Verhalten zu bewahren, so dass die Optimierung der do/while weg ist nicht viel von einer großen Sache (vorausgesetzt, die Compiler-Optimierungen sind korrekt).

12voto

Isaac Schwabacher Punkte 121

Jens Gustedts P99-Präprozessorbibliothek (ja, die Tatsache, dass es so etwas gibt, hat auch mich umgehauen!) verbessert die if(1) { ... } else Konstrukts in einer kleinen, aber bedeutsamen Weise durch die Definition des 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 zur do { ... } while(0) konstruieren, break y continue funktionieren immer noch innerhalb des angegebenen Blocks, aber die ((void)0) erzeugt einen Syntaxfehler, wenn das Semikolon nach dem Makroaufruf weggelassen wird, der sonst den nächsten Block überspringen würde. (Hier gibt es eigentlich kein "dangling else"-Problem, da die else bindet sich an die nächstgelegene if die im Makro enthalten ist).

Wenn Sie sich für die Art von Dingen interessieren, die mehr oder weniger sicher mit dem C-Präprozessor durchgeführt werden können, sollten Sie sich diese Bibliothek ansehen.

0 Stimmen

Dies ist zwar sehr clever, führt aber dazu, dass man mit Compiler-Warnungen über potenzielle "Dangling else" bombardiert wird.

2 Stimmen

Normalerweise verwenden Sie Makros, um eine geschlossene Umgebung zu schaffen, d. h. Sie verwenden niemals eine break (oder continue ) innerhalb eines Makros, um eine Schleife zu steuern, die außerhalb beginnt/endet, ist das einfach schlechter Stil und verbirgt potenzielle Ausstiegspunkte.

0 Stimmen

Außerdem gibt es eine Präprozessorbibliothek in Boost. Was ist daran so verblüffend?

9voto

Mike Meyer Punkte 89

Aus bestimmten Gründen kann ich die erste Antwort nicht kommentieren...

Einige von euch haben Makros mit lokalen Variablen gezeigt, aber niemand hat erwähnt, dass man nicht einfach irgendeinen Namen in einem Makro verwenden kann! Das wird den Benutzer eines Tages beißen! Und warum? Weil die Eingabeargumente in der Makrovorlage ersetzt werden. Und in Ihren Makrobeispielen haben Sie den wahrscheinlich am häufigsten verwendeten Variablennamen verwendet 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 verwendet

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

wird das Makro nicht die vorgesehene Variable i verwenden, 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 allgemeine Variablennamen in einem Makro!

0 Stimmen

Das übliche Muster ist, Unterstriche in Variablennamen in Makros einzufügen - zum Beispiel int __i; .

8 Stimmen

@Blaisorblade: Eigentlich ist das falsch und illegal C; führende Unterstriche sind für die Verwendung durch die Implementierung reserviert. Der Grund, warum Sie dieses "übliche Muster" gesehen haben, ist das Lesen von System-Headern ("die Implementierung"), die sich auf diesen reservierten Namensraum beschränken müssen. Für Anwendungen/Bibliotheken sollten Sie Ihre eigenen obskuren, unwahrscheinlich zu kollidierenden Namen ohne Unterstriche wählen, z.B. mylib_internal___i oder ähnlich.

2 Stimmen

@R.. Du hast Recht - ich habe das tatsächlich in einer "Anwendung" gelesen, dem Linux-Kernel, aber das ist sowieso eine Ausnahme, da er keine Standardbibliothek verwendet (technisch gesehen eine "freistehende" C-Implementierung statt einer "gehosteten").

2voto

John Nilsson Punkte 16587

Ich glaube nicht, dass es erwähnt wurde, also bedenken Sie dies

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

würde übersetzt werden in

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

bemerken, wie i++ wird von dem Makro zweimal ausgewertet. Dies kann zu einigen interessanten Fehlern führen.

18 Stimmen

Dies hat nichts mit dem do ... while(0)-Konstrukt zu tun.

2 Stimmen

Das stimmt. Aber relevant für das Thema Makros vs. Funktionen und wie man ein Makro schreibt, das sich wie eine Funktion verhält...

3 Stimmen

Ähnlich wie bei der obigen Frage handelt es sich hier nicht um eine Antwort, sondern um einen Kommentar. Zum Thema: Deshalb verwendet man Dinge nur einmal: do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)

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