#ifdef vs #if - Was ist besser / sicherer als eine Methode zum Aktivieren / Deaktivieren der Kompilierung bestimmter Codeabschnitte?

112

Das mag eine Frage des Stils sein, aber es gibt eine gewisse Kluft in unserem Entwicklerteam und ich habe mich gefragt, ob jemand andere Ideen zu diesem Thema hat ...

Grundsätzlich haben wir einige Debug-Print-Anweisungen, die wir während der normalen Entwicklung deaktivieren. Persönlich mache ich lieber Folgendes:

//---- SomeSourceFile.cpp ----

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
    int someVariable = 5;

#if(DEBUG_ENABLED)
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

Einige Mitglieder des Teams bevorzugen jedoch Folgendes:

// #define DEBUG_ENABLED

...

SomeFunction()
{
    int someVariable = 5;

#ifdef DEBUG_ENABLED
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

... welche dieser Methoden klingt für Sie besser und warum? Mein Gefühl ist, dass das erste sicherer ist, weil immer etwas definiert ist und keine Gefahr besteht, dass es andere Definitionen an anderer Stelle zerstören könnte.

Jon Cage
quelle
Hinweis: Mit #ifkönnen Sie #elifim Gegensatz zu auch auch konsistent verwenden #ifdef. Also, anstatt nur zu verwenden #define BLAH, verwenden Sie #define BLAH 1mit #if BLAH, etc ...
Andrew

Antworten:

82

Meine anfängliche Reaktion war #ifdefnatürlich , aber ich denke, dass dies #iftatsächlich einige bedeutende Vorteile hat - hier ist der Grund:

Erstens können Sie DEBUG_ENABLEDin Präprozessor- und kompilierten Tests verwenden. Beispiel - Oft möchte ich längere Zeitüberschreitungen, wenn das Debuggen aktiviert ist. Daher #ifkann ich dies mit schreiben

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

... anstatt ...

#ifdef DEBUG_MODE
  DoSomethingSlowWithTimeout(5000);
#else
  DoSomethingSlowWithTimeout(1000);
#endif

Zweitens sind Sie in einer besseren Position, wenn Sie von einer #definezu einer globalen Konstante migrieren möchten . #defines werden normalerweise von den meisten C ++ - Programmierern missbilligt.

Und drittens sagen Sie, Sie haben eine Kluft in Ihrem Team. Ich vermute, dies bedeutet, dass verschiedene Mitglieder bereits unterschiedliche Ansätze gewählt haben und Sie standardisieren müssen. Eine #ifEntscheidung, die die bevorzugte Wahl ist, bedeutet, dass der verwendete Code #ifdefkompiliert und ausgeführt wird, selbst wenn er DEBUG_ENABLEDfalsch ist. Und es ist viel einfacher, Debug-Ausgaben aufzuspüren und zu entfernen, die erzeugt werden, wenn dies nicht der Fall sein sollte, als umgekehrt.

Oh, und ein kleiner Punkt zur Lesbarkeit. Sie sollten in der Lage sein, true / false anstelle von 0/1 in Ihrem zu verwenden #define. Da es sich bei dem Wert um ein einzelnes lexikalisches Token handelt, benötigen Sie keine Klammern.

#define DEBUG_ENABLED true

anstatt

#define DEBUG_ENABLED (1)
Roddy
quelle
Die Konstante wird möglicherweise nicht zum Aktivieren / Deaktivieren des Debuggens verwendet, sodass das Auslösen eines #ifdef mit einem #define auf 0 möglicherweise nicht so harmlos ist. Richtig / falsch wurden in C99 hinzugefügt und sind in C89 / C90 nicht vorhanden.
Michael Carman
Micheal: Er / sie hat sich gegen die Verwendung von #ifdef ausgesprochen ?!
Jon Cage
7
Ja, ein Problem dabei #ifdefist, dass es mit Dingen funktioniert, die nicht definiert sind. ob sie nicht absichtlich oder wegen eines Tippfehlers definiert sind oder was Sie haben.
Bames53
6
Ihre Hinzufügung zur Antwort ist falsch. #if DEBUG_ENBALEDist kein vom Präprozessor erkannter Fehler. Wenn DEBUG_ENBALEDnicht definiert, wird es 0in #ifDirektiven auf das Token erweitert .
R .. GitHub STOP HELPING ICE
6
@R .. In vielen Compilern können Sie eine Warnung für "#if DEBUG_ENABLED" aktivieren, wenn DEBUG_ENABLED nicht definiert ist. Verwenden Sie im GCC "-Wundef". Verwenden Sie in Microsoft Visual Studio "/ w14668", um C4668 als Warnung der Stufe 1 zu aktivieren.
Will
56

Sie sind beide schrecklich. Tun Sie stattdessen Folgendes:

#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif

Wenn Sie dann Debug-Code benötigen, geben Sie ihn ein D();. Und Ihr Programm ist nicht mit abscheulichen Labyrinthen von verschmutzt #ifdef.

R .. GitHub HÖREN SIE AUF, EIS ZU HELFEN
quelle
6
@MatthieuM. Eigentlich denke ich, dass die Originalversion in Ordnung war. Das Semikolon würde als leere Anweisung interpretiert. Das Vergessen des Semikolons kann jedoch gefährlich werden.
Casey Kuball
30

#ifdef prüft nur, ob ein Token definiert ist

#define FOO 0

dann

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"
Terence Simpson
quelle
20

Wir hatten das gleiche Problem bei mehreren Dateien und es gibt immer das Problem, dass Leute vergessen, eine "Features Flag" -Datei einzuschließen (mit einer Codebasis von> 41.000 Dateien ist dies einfach).

Wenn Sie feature.h hatten:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

Aber dann haben Sie vergessen, die Header-Datei in file.cpp aufzunehmen:

#if COOL_FEATURE
    // definitely awesome stuff here...
#endif

Dann haben Sie ein Problem, der Compiler interpretiert COOL_FEATURE in diesem Fall als "false" und definiert den Code nicht. Ja, gcc unterstützt ein Flag, das einen Fehler für undefinierte Makros verursacht ... aber der meiste Code von Drittanbietern definiert oder definiert keine Funktionen, sodass dies nicht so portabel wäre.

Wir haben eine tragbare Methode zur Korrektur dieses Falls sowie zum Testen des Status eines Features eingeführt: Funktionsmakros.

Wenn Sie die obige Funktion geändert haben.h in:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE() 1

#endif // FEATURE_H

Aber dann haben Sie wieder vergessen, die Header-Datei in file.cpp aufzunehmen:

#if COOL_FEATURE()
    // definitely awseome stuff here...
#endif

Der Präprozessor wäre aufgrund der Verwendung eines undefinierten Funktionsmakros fehlerhaft gewesen.

Brent Priddy
quelle
17

Für die bedingte Kompilierung sind #if und #ifdef fast gleich, aber nicht ganz. Wenn Ihre bedingte Kompilierung von zwei Symbolen abhängt, funktioniert #ifdef nicht so gut. Angenommen, Sie haben zwei bedingte Kompilierungssymbole, PRO_VERSION und TRIAL_VERSION. Möglicherweise haben Sie Folgendes:

#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif

Mit #ifdef wird das oben Genannte viel komplizierter, insbesondere wenn der # else-Teil funktioniert.

Ich arbeite an Code, der die bedingte Kompilierung ausgiebig verwendet, und wir haben eine Mischung aus #if & #ifdef. Wir neigen dazu, # ifdef / # ifndef für den einfachen Fall und #if zu verwenden, wenn zwei oder mehr Symbole ausgewertet werden.

Mike Thompson
quelle
1
in #if definedwas ist definedes ein Schlüsselwort oder?
nmxprime
15

Ich denke, es ist eine Frage des Stils. Keiner hat wirklich einen offensichtlichen Vorteil gegenüber dem anderen.

Konsistenz ist wichtiger als jede bestimmte Wahl, daher würde ich empfehlen, dass Sie sich mit Ihrem Team zusammensetzen, einen Stil auswählen und dabei bleiben.

Derek Park
quelle
8

Ich selbst bevorzuge:

#if defined(DEBUG_ENABLED)

Da es einfacher ist, Code zu erstellen, der nach der entgegengesetzten Bedingung sucht, ist dies viel einfacher zu erkennen:

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)
Jim Buck
quelle
8
Persönlich denke ich, dass es einfacher ist, dieses kleine Ausrufezeichen zu übersehen!
Jon Cage
6
Mit Syntaxhervorhebung? :) Bei der Syntaxhervorhebung ist das "n" in "ifndef" viel schwerer zu erkennen, da es alle dieselbe Farbe hat.
Jim Buck
Okay, ich meinte, #ifndef ist leichter zu erkennen als #if! Definiert, wenn man es mit #if definiert vergleicht.
Jon Cage
@ JonCage Ich weiß, seit diesem Kommentar sind einige Jahre vergangen, aber ich möchte darauf hinweisen, dass Sie ihn so schreiben können #if ! defined, dass !er prominenter und schwer zu übersehen ist.
Pharap
@Pharap - Das sieht sicherlich nach einer Verbesserung aus :)
Jon Cage
7

Es ist eine Frage des Stils. Ich empfehle jedoch eine präzisere Vorgehensweise:

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print
#endif

debug_print("i=%d\n", i);

Sie tun dies einmal und verwenden dann immer debug_print (), um entweder zu drucken oder nichts zu tun. (Ja, dies wird in beiden Fällen kompiliert.) Auf diese Weise wird Ihr Code nicht mit Präprozessoranweisungen verstümmelt.

Wenn Sie die Warnung "Ausdruck hat keine Wirkung" erhalten und diese entfernen möchten, finden Sie hier eine Alternative:

void dummy(const char*, ...)
{}

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print dummy
#endif

debug_print("i=%d\n", i);
Lev
quelle
3
Vielleicht war das Druckmakro doch nicht das beste Beispiel - wir tun dies tatsächlich bereits in unserer Codebasis für unseren Standard-Debug-Code. Wir verwenden die # if / #ifdefined-Bits für Bereiche, in denen Sie möglicherweise zusätzliches Debugging aktivieren möchten.
Jon Cage
5

#ifSie haben die Möglichkeit, den Wert auf 0 zu setzen, um die Funktionalität auszuschalten, während Sie weiterhin feststellen, dass der Schalter vorhanden ist.
Persönlich bin ich immer #define DEBUG 1so, dass ich es entweder mit einem #if oder #ifdef fangen kann

Martin Beckett
quelle
1
Dies schlägt fehl, da #define DEBUG = 0 jetzt nicht #if ausgeführt wird, sondern #ifdef
tloach
1
Das ist der Punkt, ich kann entweder DEBUG vollständig entfernen oder es einfach auf 0 setzen, um es zu deaktivieren.
Martin Beckett
es sollte sein #define DEBUG 1. Nicht#define DEBUG=1
Keshava GN
4

#if und #define MY_MACRO (0)

Die Verwendung von #if bedeutet, dass Sie ein "Define" -Makro erstellt haben, dh etwas, das im Code durchsucht wird, der durch "(0)" ersetzt werden soll. Dies ist die "Makro-Hölle", die ich in C ++ nicht gerne sehe, weil sie den Code mit möglichen Code-Änderungen verschmutzt.

Beispielsweise:

#define MY_MACRO (0)

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

gibt den folgenden Fehler auf g ++:

main.cpp|408|error: lvalue required as left operand of assignment|
||=== Build finished: 1 errors, 0 warnings ===|

Nur ein Fehler.

Dies bedeutet, dass Ihr Makro erfolgreich mit Ihrem C ++ - Code interagiert hat: Der Aufruf der Funktion war erfolgreich. In diesem einfachen Fall ist es amüsant. Aber meine eigene Erfahrung mit Makros, die lautlos mit meinem Code spielen, ist nicht voller Freude und Erfüllung, also ...

#ifdef und #define MY_MACRO

Die Verwendung von #ifdef bedeutet, dass Sie etwas "definieren". Nicht, dass du ihm einen Wert gibst. Es ist immer noch umweltschädlich, aber zumindest wird es "durch nichts ersetzt" und von C ++ - Code nicht als verzögerte Code-Anweisung angesehen. Der gleiche Code oben, mit einer einfachen Definition, es:

#define MY_MACRO

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

Gibt die folgenden Warnungen:

main.cpp||In function int main(int, char**)’:|
main.cpp|406|error: expected unqualified-id before ‘=’ token|
main.cpp|399|error: too few arguments to function int doSomething(int)’|
main.cpp|407|error: at this point in file|
||=== Build finished: 3 errors, 0 warnings ===|

So...

Fazit

Ich würde lieber ohne Makros in meinem Code leben, aber aus mehreren Gründen (Definieren von Header Guards oder Debuggen von Makros) kann ich nicht.

Aber zumindest möchte ich sie mit meinem legitimen C ++ - Code so wenig interaktiv wie möglich gestalten. Was bedeutet, dass Sie #define ohne Wert verwenden, #ifdef und #ifndef verwenden (oder sogar #if, wie von Jim Buck vorgeschlagen), und vor allem, wenn Sie ihnen Namen geben, die so lang und so fremd sind, dass niemand, der bei klarem Verstand ist, sie verwenden wird es "durch Zufall", und das wird in keiner Weise legitimen C ++ - Code beeinflussen.

Post Scriptum

Jetzt, während ich meinen Beitrag erneut lese, frage ich mich, ob ich nicht versuchen sollte, einen Wert zu finden, der niemals richtig sein wird, um C ++ zu meiner Definition hinzuzufügen. Etwas wie

#define MY_MACRO @@@@@@@@@@@@@@@@@@

Das könnte mit #ifdef und #ifndef verwendet werden, aber Code darf nicht kompiliert werden, wenn er innerhalb einer Funktion verwendet wird ... Ich habe dies erfolgreich in g ++ versucht und es gab den Fehler:

main.cpp|410|error: stray ‘@’ in program|

Interessant. :-)

paercebal
quelle
Ich bin damit einverstanden, dass Makros gefährlich sein können, aber das erste Beispiel wäre für das Debuggen ziemlich offensichtlich und gibt natürlich nur einen Fehler. Warum würden Sie mehr erwarten? Ich habe viel schlimmere Fehler als Ergebnis von Makros gesehen ...
Jon Cage
Es ist wahr, der Unterschied zwischen einer Lösung und einer anderen ist fast trivial. Aber in diesem Fall, da es sich um zwei konkurrierende Codierungsstile handelt, kann selbst das Triviale nicht ignoriert werden, da danach nur noch der persönliche Geschmack übrig bleibt (und an diesem Punkt glaube ich, dass er nicht normalisiert werden sollte )
paercebal
4

Das ist überhaupt keine Frage des Stils. Auch die Frage ist leider falsch. Sie können diese Präprozessor-Direktiven nicht im Sinne von besser oder sicherer vergleichen.

#ifdef macro

bedeutet "wenn Makro definiert ist" oder "wenn Makro existiert". Der Wert des Makros spielt hier keine Rolle. Es kann was auch immer sein.

#if macro

wenn immer mit einem Wert vergleichen. Im obigen Beispiel ist dies der implizite Standardvergleich:

#if macro !=0

Beispiel für die Verwendung von #if

#if CFLAG_EDITION == 0
    return EDITION_FREE;
#elif CFLAG_EDITION == 1
    return EDITION_BASIC;
#else
    return EDITION_PRO;
#endif

Sie können jetzt entweder die Definition von CFLAG_EDITION in Ihren Code einfügen

#define CFLAG_EDITION 1 

oder Sie können das Makro als Compiler-Flag setzen. Auch hier sehen .

tmanthey
quelle
2

Das erste scheint mir klarer zu sein. Es scheint natürlicher, es zu einer Flagge zu machen als definiert / nicht definiert.

Axblount
quelle
2

Beide sind genau gleichwertig. Bei der idiomatischen Verwendung wird #ifdef nur verwendet, um die Definiertheit zu überprüfen (und was ich in Ihrem Beispiel verwenden würde), während #if in komplexeren Ausdrücken verwendet wird, wie z. B. #if defined (A) &&! Defined (B).

zvrba
quelle
1
Das OP fragte nicht, was zwischen "#ifdef" und "#if defined" besser ist, sondern zwischen "# ifdef / # if defined" und "#if".
Shank
1

Ein wenig OT, aber das Ein- und Ausschalten der Protokollierung mit dem Präprozessor ist in C ++ definitiv nicht optimal. Es gibt nette Protokollierungstools wie log4cxx von Apache, die Open Source sind und die Verteilung Ihrer Anwendung nicht einschränken. Sie ermöglichen es Ihnen auch, die Protokollierungsstufen ohne Neukompilierung zu ändern, haben einen sehr geringen Overhead, wenn Sie die Protokollierung deaktivieren, und geben Ihnen die Möglichkeit, die Protokollierung in der Produktion vollständig zu deaktivieren.

David Nehme
quelle
1
Ich stimme zu, und wir machen das tatsächlich in unserem Code. Ich wollte nur ein Beispiel für etwas, für das Sie #if usw. verwenden könnten
Jon Cage
1

Früher habe ich verwendet #ifdef, aber als ich zur Dokumentation zu Doxygen wechselte, stellte ich fest, dass auskommentierte Makros nicht dokumentiert werden können (oder zumindest Doxygen eine Warnung ausgibt). Dies bedeutet, dass ich die derzeit nicht aktivierten Feature-Switch-Makros nicht dokumentieren kann.

Obwohl es möglich ist, die Makros nur für Doxygen zu definieren, bedeutet dies, dass auch die Makros in den nicht aktiven Teilen des Codes dokumentiert werden. Ich persönlich möchte die Funktionsschalter zeigen und ansonsten nur dokumentieren, was aktuell ausgewählt ist. Darüber hinaus macht es den Code ziemlich chaotisch, wenn es viele Makros gibt, die nur definiert werden müssen, wenn Doxygen die Datei verarbeitet.

Daher ist es in diesem Fall besser, immer die Makros zu definieren und zu verwenden #if.

StefanB
quelle
0

Ich habe immer #ifdef und Compiler-Flags verwendet, um es zu definieren ...

tloach
quelle
Gibt es einen bestimmten Grund (aus Neugier)?
Jon Cage
2
Um ehrlich zu sein, habe ich nie darüber nachgedacht - wie es an Orten gemacht wurde, an denen ich gearbeitet habe. Es hat den Vorteil, dass Sie anstelle einer Codeänderung für einen Produktionsbuild nur "make DEBUG" für das Debugging oder "make PRODUCTION" für den regulären
tloach
0

Alternativ können Sie eine globale Konstante deklarieren und anstelle des Präprozessors #if das C ++ if verwenden. Der Compiler sollte die nicht verwendeten Zweige für Sie optimieren, und Ihr Code wird sauberer.

Hier ist, was C ++ Gotchas von Stephen C. Dewhurst über die Verwendung von # if's sagt.

Dima
quelle
1
Das ist eine miese Lösung, sie hat die folgenden Probleme: 1. Funktioniert nur in Funktionen, Sie können nicht benötigte Klassenvariablen usw. nicht entfernen. 2. Compiler können Warnungen vor nicht erreichbarem Code auslösen. 3. Code im if muss noch kompiliert werden, was bedeutet Sie müssen alle Ihre Debug-Funktionen definiert halten, etc.
Don Neufeld
Zunächst ging es speziell um das Debuggen von printfs, sodass nicht benötigte Klassenvariablen hier kein Problem darstellen. Zweitens sollten Sie angesichts der Fähigkeiten moderner Compiler #ifdefs so wenig wie möglich verwenden. In den meisten Fällen können Sie stattdessen Build-Konfigurationen oder Vorlagenspezialisierungen verwenden.
Dima
0

Es gibt einen Unterschied zwischen verschiedenen Möglichkeiten, eine bedingte Definition für den Treiber anzugeben:

diff <( echo | g++ -DA= -dM -E - ) <( echo | g++ -DA -dM -E - )

Ausgabe:

344c344
< #define A 
---
> #define A 1

Dies bedeutet, dass dies -DAein Synonym für ist -DA=1und wenn der Wert weggelassen wird, kann dies zu Problemen bei der #if AVerwendung führen.

Tomilov Anatoliy
quelle
0

Ich mag es, #define DEBUG_ENABLED (0)wenn Sie mehrere Debug-Ebenen möchten. Beispielsweise:

#define DEBUG_RELEASE (0)
#define DEBUG_ERROR (1)
#define DEBUG_WARN (2)
#define DEBUG_MEM (3)
#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL (DEBUG_RELEASE)
#endif
//...

//now not only
#if (DEBUG_LEVEL)
//...
#endif

//but also
#if (DEBUG_LEVEL >= DEBUG_MEM)
LOG("malloc'd %d bytes at %s:%d\n", size, __FILE__, __LINE__);
#endif

Erleichtert das Debuggen von Speicherlecks, ohne dass all diese Protokollzeilen Sie beim Debuggen anderer Dinge behindern.

Das #ifndefUmrunden der Definition erleichtert auch die Auswahl einer bestimmten Debug-Ebene in der Befehlszeile:

make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc

Wenn dies nicht #ifdefder Fall wäre, würde ich einen Vorteil daraus ziehen, da das Compiler / Make-Flag von dem in der Datei überschrieben würde. Sie müssen sich also nicht darum kümmern, den Header zurückzusetzen, bevor Sie das Commit ausführen.

memtha
quelle