Warum scheinbar bedeutungslose do-while- und if-else-Anweisungen in Makros verwenden?


788

In vielen C / C ++ - Makros sehe ich den Code des Makros in einer scheinbar bedeutungslosen do whileSchleife. 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 whilemacht. Warum nicht einfach ohne schreiben?

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

2
Für das Beispiel mit dem else würde ich voidam Ende einen Ausdruck vom Typ hinzufügen ... wie ((void) 0) .
Phil1970

1
Erinnern Sie daran, dass das do whileKonstrukt nicht mit return-Anweisungen kompatibel ist, sodass das if (1) { ... } else ((void)0)Konstrukt in Standard C besser kompatibel ist. In GNU C bevorzugen Sie das in meiner Antwort beschriebene Konstrukt.
Cœur

Antworten:


829

Die do ... whileund if ... elsesind dazu da, damit ein Semikolon nach Ihrem Makro immer dasselbe bedeutet. Angenommen, Sie hatten so etwas wie Ihr zweites Makro.

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

Wenn Sie nun BAR(X);eine if ... elseAnweisung verwenden, bei der die Textkörper der if-Anweisung nicht in geschweifte Klammern gesetzt sind, erhalten Sie eine schlechte Überraschung.

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

Der obige Code würde sich erweitern in

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

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.

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

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.

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

Die obige Version von bar BARerweitert den obigen Code in das Folgende, was syntaktisch korrekt ist.

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

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 ... whiledas Makro als einzelne Anweisung zu verwenden, die ohne Verwirrung ein Semikolon enthält.

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

Sie müssen nicht verwenden do ... while, Sie könnten auch etwas damit kochen if ... else, obwohl es, wenn es if ... elseinnerhalb eines erweitert wird if ... 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 .

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

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, BARals tatsächliche Funktion und nicht als Makro zu deklarieren .

Zusammenfassend lässt sich sagen, do ... whiledass 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.


18
Ist dies nicht ein starkes Argument, um immer Klammern in if, while und for-Anweisungen zu verwenden? Wenn Sie dies immer tun (wie dies beispielsweise für MISRA-C erforderlich ist), verschwindet das oben beschriebene Problem.
Steve Melnikoff

17
Das Komma-Beispiel sollte #define BAR(X) (f(X), g(X))anders lauten, da die Priorität des Operators die Semantik durcheinander bringen kann.
Stewart

20
@DawidFerenczy: Obwohl sowohl Sie als auch ich vor viereinhalb Jahren einen guten Punkt gemacht haben, müssen wir in der realen Welt leben. Wenn wir nicht garantieren können, dass alle ifAnweisungen usw. in unserem Code geschweifte Klammern verwenden, ist das Umschließen solcher Makros eine einfache Möglichkeit, Probleme zu vermeiden.
Steve Melnikoff

8
Hinweis: Das if(1) {...} else void(0)Formular ist sicherer als das do {...} 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, wenn MYMACROdefiniert 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.
Chris Kline

5
@ace 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 demonstriert
Chris Kline

153

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

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

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:

if(bIsOk)
   MY_MACRO(42) ;

Dieses Makro könnte erweitert werden als:

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

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

Die gFunktion wird unabhängig vom Wert von ausgeführt bIsOk.

Dies bedeutet, dass wir dem Makro einen Bereich hinzufügen müssen:

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

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

Erstellen Sie einen gültigen Code 2

Wenn das Makro so etwas wie:

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

Wir könnten ein anderes Problem im folgenden Code haben:

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

Weil es sich erweitern würde als:

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, 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:

#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 korrekt erweitert 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 kompiliert und optimiert

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

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

   // Etc.
}

6
Beachten Sie, dass das Ändern von Makros in Inline-Funktionen einige vordefinierte Standardmakros ändert, z. B. zeigt der folgende Code eine Änderung von FUNCTION und LINE : #include <stdio.h> #define Fmacro () printf ("% s% d \ n", FUNCTION , LINE ) inline void Finline () {printf ("% s% d \ n", FUNCTION , LINE ); } int main () {Fmacro (); Finline (); return 0; } (Fettgedruckte Begriffe sollten durch doppelte Unterstriche eingeschlossen werden - schlechter Formatierer!)
Gnubie

6
Bei dieser Antwort gibt es eine Reihe kleinerer, aber nicht völlig unwichtiger Probleme. Zum Beispiel: void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; }ist nicht die richtige Erweiterung; Das xin der Erweiterung sollte 32 sein. Ein komplexeres Problem ist, was die Erweiterung von ist MY_MACRO(i+7). Und ein anderer ist die Erweiterung von MY_MACRO(0x07 << 6). Es gibt eine Menge, die gut ist, aber es gibt einige ungepunktete Ichs und ungekreuzte T's.
Jonathan Leffler

@Gnubie: Ich für den Fall, dass Sie immer noch hier sind und dies bis jetzt noch nicht herausgefunden haben: Sie können Sternchen und Unterstrichen in Kommentaren mit Backslashes \_\_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.
Scott

1
Ich weiß zu schätzen, dass mein Kommentar sechs Jahre zu spät ist, aber die meisten C-Compiler haben keine Inline- inlineFunktionen (wie vom Standard zugelassen)
Andrew

53

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

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

if (test) FOO( baz);

erweitert sich zu:

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

Dies ist syntaktisch korrekt, sodass kein Compilerfehler vorliegt, hat jedoch die wahrscheinlich unbeabsichtigte Folge, dass g () immer aufgerufen wird.


22
"wahrscheinlich unbeabsichtigt"? Ich hätte "sicherlich unbeabsichtigt" gesagt, sonst muss der Programmierer herausgenommen und erschossen werden (im Gegensatz zu einer rundlichen Züchtigung mit einer Peitsche).
Lawrence Dol

4
Oder sie erhalten eine Gehaltserhöhung, wenn sie für eine Agentur mit drei Buchstaben arbeiten und diesen Code heimlich in ein weit verbreitetes Open-Source-Programm
einfügen

2
Und dieser Kommentar erinnert mich nur an die Goto-Fail-Zeile im letzten Fehler bei der Überprüfung von SSL-Zertifikaten, der in Apple OSes gefunden wurde
Gerard Sexton

23

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 ... whiledem if ... elseKonstrukt vorzuziehen .

Das Problem des if ... elseKonstrukts ist, dass Sie nicht gezwungen werden , das Semikolon einzufügen. Wie in diesem Code:

FOO(1)
printf("abc");

Obwohl wir das Semikolon (aus Versehen) weggelassen haben, wird der Code auf erweitert

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

und wird stillschweigend kompiliert (obwohl einige Compiler möglicherweise eine Warnung für nicht erreichbaren Code ausgeben). Die printfAnweisung wird jedoch niemals ausgeführt.

do ... whileKonstrukt hat kein solches Problem, da das einzige gültige Token nach dem while(0)ein Semikolon ist.


2
@RichardHansen: Immer noch nicht so gut, weil man beim Betrachten des Makroaufrufs nicht weiß, ob er zu einer Anweisung oder zu einem Ausdruck erweitert wird. Wenn jemand das später annimmt, kann sie schreiben, FOO(1),x++;was uns wieder ein falsches Positiv gibt. Einfach benutzen do ... whileund fertig.
Yakov Galka

1
Die Dokumentation des Makros zur Vermeidung von Missverständnissen sollte ausreichen. Ich bin damit einverstanden, dass dies do ... while (0)vorzuziehen ist, aber es hat einen Nachteil: A breakoder continuesteuert die do ... while (0)Schleife, nicht die Schleife, die den Makroaufruf enthält. Der ifTrick hat also immer noch Wert.
Richard Hansen

2
Ich sehe nicht, wo Sie ein breakoder ein platzieren könnten continue, das sich in Ihrer Makro- do {...} while(0)Pseudo-Schleife befindet. Selbst im Makroparameter würde es einen Syntaxfehler geben.
Patrick Schlüter

5
Ein weiterer Grund für die Verwendung do { ... } while(0)anstelle des if whateverKonstrukts ist die idiomatische Natur. Das do {...} 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 beim ifKonstrukt. Es ist daher weniger mühsam, bei der Codeüberprüfung zu groken.
Patrick Schlüter

1
@tristopia: Ich habe Leute gesehen, die Makros geschrieben haben, die Codeblöcke als Argumente verwenden (was ich nicht unbedingt empfehle). Zum Beispiel : #define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0. Es könnte wie folgt verwendet werden CHECK(system("foo"), break;);, wenn sich das break;auf die Schleife beziehen soll, die den CHECK()Aufruf einschließt .
Richard Hansen

16

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:

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

oder noch exotischer:

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

Dies funktioniert zwar gut mit separaten Anweisungen, funktioniert jedoch nicht in Fällen, in denen Variablen erstellt und als Teil von #define:

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

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


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

15
@Marius: Falsch; der Komma - Operator ist ein Sequenzpunkt und dadurch tut Garantieausführungsreihenfolge. Ich vermute, Sie haben es mit dem Komma in Funktionsargumentlisten verwechselt.
R .. GitHub STOP HELPING ICE

2
Der zweite exotische Vorschlag machte meinen Tag.
Spidey

Ich wollte nur hinzufügen, dass Compiler gezwungen sind, das vom Programm beobachtbare Verhalten beizubehalten, sodass die Optimierung von do / while away keine große Sache ist (vorausgesetzt, die Compiler-Optimierungen sind korrekt).
Marco A.

@ MarcoA. Während Sie richtig liegen, habe ich in der Vergangenheit festgestellt, dass die Compileroptimierung, während die Funktion des Codes genau erhalten bleibt, aber durch das Ändern von Zeilen, die im singulären Kontext nichts zu tun scheinen, Multithread-Algorithmen zerstört. Ein typisches Beispiel Peterson's Algorithm.
Marius

11

Die P99-Präprozessorbibliothek von Jens Gustedt (ja, die Tatsache, dass so etwas existiert, hat mich auch umgehauen!) Verbessert das if(1) { ... } elseKonstrukt auf eine kleine, aber signifikante Weise, indem Folgendes definiert wird:

#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 zum do { ... } while(0)Konstrukt breakund continueinnerhalb 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 das elsean das nächste bindet if, 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.


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

1
Normalerweise verwenden Sie Makros, um eine geschlossene Umgebung zu erstellen. Das heißt, Sie verwenden niemals ein break(oder continue) innerhalb eines Makros, um eine Schleife zu steuern, die außerhalb begann / endete. Das ist nur ein schlechter Stil und verbirgt potenzielle Austrittspunkte.
Mirabilos

Es gibt auch eine Präprozessor-Bibliothek in Boost. Was ist umwerfend daran?
Rene

Das Risiko else ((void)0)besteht darin, dass jemand schreibt YOUR_MACRO(), f();und es syntaktisch gültig ist, aber niemals anruft f(). Mit ist do whilees ein Syntaxfehler.
Melpomene

@ Melpomene also was ist mit else do; while (0)?
Carl Lei

8

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

#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);
}

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!


Das übliche Muster ist das Hinzufügen von Unterstrichen in Variablennamen in Makros - zum Beispiel int __i;.
Blaisorblade

8
@Blaisorblade: Eigentlich ist das falsch und illegal C; Führende Unterstriche sind für die Implementierung reserviert. Der Grund, warum Sie dieses "übliche Muster" gesehen haben, ist das Lesen von Systemheadern ("die Implementierung"), die sich auf diesen reservierten Namespace beschränken müssen. Für Anwendungen / Bibliotheken sollten Sie Ihre eigenen obskuren Namen auswählen, bei denen es unwahrscheinlich ist, dass sie ohne Unterstriche kollidieren, z mylib_internal___i. B. oder ähnliches.
R .. GitHub STOP HELPING ICE

2
@R .. Sie haben Recht - ich habe dies tatsächlich in einer '' Anwendung '', dem Linux-Kernel, gelesen, aber es ist trotzdem eine Ausnahme, da es keine Standardbibliothek verwendet (technisch gesehen stattdessen eine '' freistehende '' C-Implementierung eines gehosteten).
Blaisorblade

3
@R .. das ist nicht ganz richtig: führende Unterstriche gefolgt von einem Großbuchstaben oder einem zweiten Unterstrich sind für die Implementierung in allen Kontexten reserviert. Führende Unterstriche, gefolgt von etwas anderem, sind im lokalen Bereich nicht reserviert.
Leushenko

@Leushenko: Ja, aber die Unterscheidung ist so subtil, dass ich es am besten finde, den Leuten zu sagen, dass sie solche Namen überhaupt nicht verwenden sollen. Die Leute, die die Subtilität verstehen, wissen vermutlich bereits, dass ich die Details beschönige. :-)
R .. GitHub STOP HELPING ICE

7

Erläuterung

do {} while (0)und if (1) {} elsesollen sicherstellen, dass das Makro auf nur 1 Befehl 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 ifSteueranweisung ausgeführt werden. Dies wird bei der Verwendung von do {} while (0)und vermieden if (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 dies if (1) {} elselösen, indem Sie einfach Folgendes verwenden ({}):

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

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

return FOO("X");

3
Die Verwendung von Block-Clamping {} im Makro würde ausreichen, um den Makrocode so zu bündeln, dass alle für denselben if-Bedingungspfad ausgeführt werden. Das Do-While-Around wird zum Erzwingen eines Semikolons an Stellen verwendet, an denen das Makro verwendet wird. Auf diese Weise wird das Makro erzwungen und verhält sich funktionsähnlicher. Dies schließt die Anforderung für das nachfolgende Semikolon ein, wenn es verwendet wird.
Alexander Stohr

2

Ich glaube nicht, dass es erwähnt wurde, also denken Sie darüber nach

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

würde übersetzt werden in

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

Beachten Sie, wie i++das Makro zweimal auswertet. Dies kann zu interessanten Fehlern führen.


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

2
Wahr. Aber relevant für das Thema Makros vs. Funktionen und wie man ein Makro schreibt, das sich als Funktion verhält ...
John Nilsson

2
Ähnlich wie oben ist dies keine Antwort, sondern ein Kommentar. Zum Thema: Deshalb benutzt du Sachen nur einmal:do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)
Mirabilos
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.