webentwicklung-frage-antwort-db.com.de

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

Dies 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 der Sache hatte ...

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

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

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
    int someVariable = 5;

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

Einige 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? Meiner Meinung nach ist die erste sicherer, da immer etwas definiert ist und es keine Gefahr gibt, dass andere Definitionen an anderer Stelle zerstört werden.

107
Jon Cage

Meine erste Reaktion war#ifdef, natürlich , aber ich denke, #if hat tatsächlich einige bedeutende Vorteile - hier ist der Grund:

Erstens können Sie DEBUG_ENABLED in kompilierten Preprozessor und - Tests verwenden. Beispiel - Oft möchte ich längere Zeitüberschreitungen, wenn Debug aktiviert ist. Mit #if kann ich dies 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 einem #define zu einer globalen Konstante migrieren möchten. #defines werden von den meisten C++ - Programmierern normalerweise nicht gern gesehen.

Und drittens sagen Sie, Sie haben eine Kluft in Ihrem Team. Meine Vermutung ist, dass verschiedene Mitglieder bereits unterschiedliche Ansätze gewählt haben und dass Sie standardisieren müssen. Die Entscheidung, dass #if die bevorzugte Wahl ist, bedeutet, dass Code, der #ifdef verwendet, auch dann kompiliert wird, wenn DEBUG_ENABLED falsch ist. Und es ist viel einfacher, Debug-Ausgaben zu finden und zu entfernen, die erzeugt werden, wenn dies nicht anders sein sollte als umgekehrt.

Oh, und ein kleiner Lesbarkeitspunkt. Sie sollten in der Lage sein, true/false anstelle von 0/1 in Ihrem #define zu verwenden, und 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)
78
Roddy

Sie sind beide schrecklich. Tun Sie dies stattdessen:

#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 in D(); ein. Und Ihr Programm ist nicht mit hässlichen Irrgärten von #ifdef verschmutzt.

54
R..

#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"
26
Terence Simpson

Wir hatten das gleiche Problem bei mehreren Dateien und es gibt immer ein Problem, wenn Benutzer vergessen haben, eine "Merkmalsflag" -Datei hinzuzufügen (mit einer Codebase von> 41.000 Dateien ist dies einfach möglich).

Wenn Sie feature.h hätten:

#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 einzuschließen:

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

Wenn Sie ein Problem haben, interpretiert der Compiler, dass COOL_FEATURE in diesem Fall undefiniert ist, als "false" und kann den Code nicht einfügen. Ja, gcc unterstützt ein Flag, das einen Fehler für undefinierte Makros verursacht. Die meisten Drittanbieter-Codes definieren oder definieren jedoch keine Features, so dass dies nicht tragbar wäre.

Wir haben eine tragbare Methode zur Korrektur dieses Falls übernommen und den Status eines Features geprüft: Funktionsmakros.

wenn Sie das obige Feature.h geändert haben:

#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 einzuschließen:

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

Der Präprozessor wäre wegen der Verwendung eines undefinierten Funktionsmakros ausgefallen. 

18
Brent Priddy

#If und #ifdef sind fast für die Zwecke der bedingten Kompilierung 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. Sie könnten etwa Folgendes haben:

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

Wenn Sie #ifdef verwenden, wird das oben genannte Verfahren viel komplizierter, vor allem, wenn Sie den #else-Teil zum Laufen bringen. 

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

15
Mike Thompson

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

Konsistenz ist wichtiger als eine der beiden Optionen. Ich empfehle Ihnen, dass Sie sich mit Ihrem Team treffen und einen Stil wählen und dabei bleiben.

14
Derek Park

Ich selbst bevorzuge:

#if defined(DEBUG_ENABLED)

Da es einfacher ist, Code zu erstellen, der nach der gegenteiligen Bedingung sucht, ist das Erkennen viel einfacher:

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)
7
Jim Buck

Es ist eine Frage des Stils. Ich empfehle jedoch eine prägnantere 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 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 durch Präprozessoranweisungen gestört.

Wenn Sie die Warnung erhalten, dass "Ausdruck keine Wirkung hat" und Sie loswerden 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);
6
Lev

#if gibt Ihnen die Möglichkeit, den Wert auf 0 zu setzen, um die Funktionalität auszuschalten, während Sie dennoch feststellen, dass der Schalter vorhanden ist.
Ich persönlich #define DEBUG 1, so dass ich es entweder mit #if oder #ifdef fangen kann

4
Martin Beckett

#if und #define MY_MACRO (0)

Die Verwendung von #if bedeutet, dass Sie ein "define" -Makro erstellt haben, d. H. Etwas, das in dem Code gesucht wird, der durch "(0)" ersetzt werden soll. Dies ist die "Makro-Hölle", die ich in C++ nicht sehen möchte, weil sie den Code mit potenziellen Code-Änderungen verschmutzt.

Zum Beispiel:

#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 in g ++:

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

Nur Eins Fehler.

Das bedeutet, dass Ihr Makro erfolgreich mit Ihrem C++ - Code interagierte: 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 Vollendung, also ...

#ifdef und #define MY_MACRO

Mit #ifdef "definieren" Sie etwas. Nicht, dass Sie ihm einen Wert geben. Es ist immer noch umweltverschmutzend, aber zumindest wird es "durch nichts ersetzt" und nicht von C++ - Code als verzögerte Code-Anweisung gesehen. Derselbe Code oben mit einer einfachen Definition:

#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 aus:

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 Debug-Makros) kann ich nicht.

Zumindest möchte ich sie jedoch mit meinem legitimen C++ - Code so wenig interaktiv wie möglich gestalten. Was bedeutet, #define ohne Wert zu verwenden, #ifdef und #ifndef (oder sogar #if, wie von Jim Buck vorgeschlagen), und vor allem, wenn man ihnen so lange Namen gibt und sie so fremd sind, dass niemand, der sich im Verstand befindet, Gebrauch machen wird es "zufällig", und das beeinflusst in keiner Weise legitimen C++ - Code.

Post Scriptum

Nun, da ich meinen Beitrag noch einmal lese, frage ich mich, ob ich nicht versuchen sollte, einen Wert zu finden, der niemals in der richtigen Weise in C++ enthalten sein wird, um ihn zu definieren. So etwas wie

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

das könnte mit #ifdef und #ifndef verwendet werden, aber Code nicht kompilieren lassen, wenn er in einer Funktion verwendet wird ... Ich habe dies erfolgreich mit g ++ versucht, und es wurde der Fehler ausgegeben:

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

Interessant.:-)

3
paercebal

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

2
David Nehme

Das ist überhaupt keine Stilfrage. Auch die Frage ist leider falsch. Sie können diese Präprozessoranweisungen nicht im Sinne von besser oder sicherer vergleichen.

#ifdef macro

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

#if macro

wenn immer mit einem Wert vergleichen. Im obigen Beispiel handelt es sich um den standardmäßigen impliziten Vergleich:

#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 siehe hier .

2
tmanthey

Die erste scheint mir klarer zu sein. Es scheint natürlicher, es als Flag zu definieren, als definiert/nicht definiert.

2
axblount

Beide sind genau gleichwertig. In idiomatischer Verwendung wird #ifdef nur zur Überprüfung der Definiertheit verwendet (und was in Ihrem Beispiel verwendet würde), wohingegen #if in komplexeren Ausdrücken verwendet wird, z.

2
zvrba

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

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

Auch das #ifndef um das Define erleichtert das Auswählen einer bestimmten Debug-Ebene in der Befehlszeile:

make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc

Wäre dies nicht der Fall, würde ich #ifdef nutzen, da das Compiler/Make-Flag von dem in der Datei überschrieben würde. Sie müssen sich also keine Gedanken darüber machen, den Header zurückzusetzen, bevor Sie das Commit ausführen.

0
memtha

Es gibt einen Unterschied für den Fall, dass der Fahrer eine bedingte Definition definiert:

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

ausgabe:

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

Dies bedeutet, dass -DA ein Synonym für -DA=1 ist. Wenn der Wert weggelassen wird, kann dies bei Verwendung von #if A zu Problemen führen.

0
Orient

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

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

0
Dima

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

0
tloach

Ich habe früher #ifdef verwendet, aber als ich zu Doxygen zur Dokumentation gewechselt bin, habe ich festgestellt, dass auskommentierte Makros nicht dokumentiert werden können (oder zumindest erzeugt Doxygen eine Warnung). Dies bedeutet, dass ich die Makros, die derzeit nicht aktiviert sind, nicht dokumentieren können.

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 anzeigen und ansonsten nur dokumentieren, was aktuell ausgewählt ist. Außerdem macht es den Code ziemlich chaotisch, wenn viele Makros nur definiert werden müssen, wenn Doxygen die Datei verarbeitet.

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

0
StefanB