#ifdef vs #if - qual è il metodo migliore / più sicuro per abilitare / disabilitare la compilazione di particolari sezioni di codice?


112

Potrebbe essere una questione di stile, ma c'è un po 'di divario nel nostro team di sviluppo e mi chiedevo se qualcun altro avesse qualche idea in merito ...

Fondamentalmente, abbiamo alcune istruzioni di stampa di debug che disattiviamo durante il normale sviluppo. Personalmente preferisco fare quanto segue:

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

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
    int someVariable = 5;

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

Alcuni membri del team preferiscono però quanto segue:

// #define DEBUG_ENABLED

...

SomeFunction()
{
    int someVariable = 5;

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

... quale di questi metodi ti suona meglio e perché? La mia sensazione è che la prima sia più sicura perché c'è sempre qualcosa di definito e non c'è pericolo che possa distruggere altre definizioni altrove.


Nota: con #if, puoi anche usare #elifin modo coerente, a differenza di #ifdef. Quindi, invece di usare #define BLAH, usa #define BLAH 1con #if BLAH, ecc ...
Andrew

Risposte:


82

La mia reazione iniziale è stata #ifdef, ovviamente , ma penso che in #ifrealtà abbia alcuni vantaggi significativi per questo - ecco perché:

Innanzitutto, puoi usarlo DEBUG_ENABLEDnel preprocessore e nei test compilati. Esempio: spesso desidero timeout più lunghi quando il debug è abilitato, quindi #ifposso scrivere questo

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

... invece di ...

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

In secondo luogo, sei in una posizione migliore se vuoi migrare da a #definea una costante globale. #definedi solito sono disapprovati dalla maggior parte dei programmatori C ++.

E, terzo, dici di avere una divisione nella tua squadra. La mia ipotesi è che questo significhi che membri diversi hanno già adottato approcci diversi e che è necessario standardizzare. Decidere che #ifè la scelta preferita significa che il codice che usa #ifdefverrà compilato e eseguito anche quando DEBUG_ENABLEDè falso. Ed è molto più facile rintracciare e rimuovere l'output di debug che viene prodotto quando non dovrebbe essere che viceversa.

Oh, e un piccolo punto di leggibilità. Dovresti essere in grado di usare true / false anziché 0/1 nel tuo #define, e poiché il valore è un singolo token lessicale, è l'unica volta che non hai bisogno di parentesi attorno ad esso.

#define DEBUG_ENABLED true

invece di

#define DEBUG_ENABLED (1)

La costante potrebbe non essere utilizzata per abilitare / disabilitare il debug, quindi l'attivazione di un #ifdef con un #define su 0 potrebbe non essere così benigna. Per quanto riguarda vero / falso, quelli sono stati aggiunti in C99 e non esistono in C89 / C90.
Michael Carman,

Micheal: Lui / lei stava sostenendo contro l'uso di #ifdef?!
Jon Cage,

7
Sì, un problema #ifdefè che funziona con cose che non sono definite; se non sono definiti intenzionalmente oa causa di un errore di battitura o cosa hai.
bames53

6
La tua aggiunta alla risposta è sbagliata. #if DEBUG_ENBALEDnon è un errore rilevato dal preprocessore. Se DEBUG_ENBALEDnon è definito, si espande al token 0nelle #ifdirettive.
R .. GitHub SMETTA DI AIUTARE IL GHIACCIO

6
@R .. In molti compilatori è possibile abilitare un avviso per "#if DEBUG_ENABLED" quando DEBUG_ENABLED non è definito. In GCC utilizzare "-Wundef". In Microsoft Visual Studio utilizzare "/ w14668" per attivare C4668 come avviso di livello 1.
Il

56

Sono entrambi orribili. Invece, fai questo:

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

Quindi ogni volta che hai bisogno del codice di debug, inseriscilo D();. E il tuo programma non è inquinato da orribili labirinti di #ifdef.


6
@MatthieuM. In realtà, penso che la versione originale andasse bene. Il punto e virgola verrebbe interpretato come un'istruzione vuota. Tuttavia, dimenticare il punto e virgola potrebbe renderlo pericoloso.
Casey Kuball

31

#ifdef controlla solo se un token è definito, dato

#define FOO 0

poi

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"

20

Abbiamo avuto lo stesso problema su più file e c'è sempre il problema con le persone che dimenticano di includere un file "flag di funzionalità" (con una base di codice di> 41.000 file è facile).

Se avevi feature.h:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

Ma poi hai dimenticato di includere il file di intestazione in file.cpp:

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

Quindi hai un problema, il compilatore interpreta COOL_FEATURE che non è definito come "falso" in questo caso e non riesce a includere il codice. Sì, gcc supporta un flag che causa un errore per le macro non definite ... ma la maggior parte del codice di terze parti definisce o non definisce le funzionalità, quindi non sarebbe così portabile.

Abbiamo adottato un modo portatile per correggere questo caso, oltre a testare lo stato di una funzionalità: le macro delle funzioni.

se hai modificato il file feature.h sopra in:

#ifndef FEATURE_H
#define FEATURE_H

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

#endif // FEATURE_H

Ma poi ti sei nuovamente dimenticato di includere il file di intestazione in file.cpp:

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

Il preprocessore avrebbe avuto un errore a causa dell'utilizzo di una macro di funzione non definita.


17

Ai fini della compilazione condizionale, #if e #ifdef sono quasi uguali, ma non del tutto. Se la tua compilazione condizionale dipende da due simboli, anche #ifdef non funzionerà. Ad esempio, supponi di avere due simboli di compilazione condizionale, PRO_VERSION e TRIAL_VERSION, potresti avere qualcosa del genere:

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

Usare #ifdef quanto sopra diventa molto più complicato, soprattutto per far funzionare la parte #else.

Lavoro su codice che utilizza ampiamente la compilazione condizionale e abbiamo un misto di #if e #ifdef. Tendiamo a usare # ifdef / # ifndef per il caso semplice e #if ogni volta che vengono valutati due o più simboli.


1
in #if definedche cosa è defineduna parola chiave o?
nmxprime

15

Penso che sia tutta una questione di stile. Nessuno dei due ha davvero un evidente vantaggio sull'altro.

La coerenza è più importante di entrambe le scelte particolari, quindi ti consiglio di riunirti con la tua squadra e scegliere uno stile e attenersi ad esso.


8

Io stesso preferisco:

#if defined(DEBUG_ENABLED)

Dal momento che rende più facile creare codice che cerchi la condizione opposta molto più facile da individuare:

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)

8
Personalmente penso che sia più facile perdere quel piccolo punto esclamativo!
Jon Cage,

6
Con l'evidenziazione della sintassi? :) Nell'evidenziazione della sintassi, la "n" in "ifndef" è molto più difficile da individuare poiché è dello stesso colore.
Jim Buck,

Ok, volevo dire che #ifndef è più facile da individuare rispetto a #if! Defined quando stai confrontando con #if defined .. ma dato tutto #if definito / # se! Definito vs # ifdef / # ifndef, entrambi sono ugualmente leggibili!
Jon Cage,

@JonCage So che sono passati alcuni anni da questo commento ma ci tengo a precisare che puoi scriverlo #if ! definedper renderlo !più evidente e difficile da perdere.
Pharap

@Pharap - Sembra sicuramente un miglioramento :)
Jon Cage

7

È una questione di stile. Ma consiglio un modo più conciso per farlo:

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

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

Fai questo una volta, quindi usa sempre debug_print () per stampare o non fare nulla. (Sì, questo verrà compilato in entrambi i casi.) In questo modo, il tuo codice non sarà confuso con le direttive del preprocessore.

Se ricevi l'avviso "l'espressione non ha effetto" e desideri eliminarlo, ecco un'alternativa:

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

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

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

3
Forse la macro di stampa non era il miglior esempio dopotutto - in realtà lo facciamo già nella nostra base di codice per il nostro codice di debug più standard. Usiamo i bit #if / #ifdefined per le aree in cui potresti voler attivare il debug extra ..
Jon Cage,

5

#ifti dà la possibilità di impostarlo su 0 per disattivare la funzionalità, pur rilevando che l'interruttore è presente.
Personalmente l'ho sempre #define DEBUG 1così posso prenderlo con un #if o #ifdef


1
Questo fallisce, poiché #define DEBUG = 0 ora non eseguirà #if ma eseguirà #ifdef
tloach

1
Questo è il punto, posso rimuovere completamente DEBUG o semplicemente impostarlo su 0 per disabilitarlo.
Martin Beckett,

dovrebbe essere #define DEBUG 1. Non#define DEBUG=1
Keshava GN

4

#if e #define MY_MACRO (0)

Usare #if significa che hai creato una macro "define", cioè qualcosa che verrà cercato nel codice per essere sostituito da "(0)". Questo è l '"inferno delle macro" che odio vedere in C ++, perché inquina il codice con potenziali modifiche al codice.

Per esempio:

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

dà il seguente errore su g ++:

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

Uno solo errore.

Ciò significa che la tua macro ha interagito con successo con il tuo codice C ++: la chiamata alla funzione ha avuto successo. In questo semplice caso, è divertente. Ma la mia esperienza con le macro che giocano silenziosamente con il mio codice non è piena di gioia e appagamento, quindi ...

#ifdef e #define MY_MACRO

Usare #ifdef significa "definire" qualcosa. Non che tu gli dia un valore. È ancora inquinante, ma almeno sarà "sostituito da niente" e non sarà visto dal codice C ++ come dichiarazione di codice lagitimate. Lo stesso codice sopra, con una semplice definizione, esso:

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

Fornisce le seguenti avvertenze:

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 ===|

Così...

Conclusione

Preferisco vivere senza macro nel mio codice, ma per diversi motivi (definizione di protezioni per intestazioni o macro di debug), non posso.

Ma almeno, mi piace renderli il meno interattivi possibile con il mio codice C ++ legittimo. Il che significa usare #define senza valore, usare #ifdef e #ifndef (o anche #if definito come suggerito da Jim Buck) e, soprattutto, dare loro nomi così lunghi e così alieni che nessuno sano di mente userà "per caso", e questo non influirà in alcun modo sul codice C ++ legittimo.

Post scriptum

Ora, mentre sto rileggendo il mio post, mi chiedo se non dovrei provare a trovare qualche valore che non sarà mai corretto in C ++ da aggiungere alla mia definizione. Qualcosa di simile a

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

che potrebbe essere usato con #ifdef e #ifndef, ma non lasciare che il codice si compili se usato all'interno di una funzione ... L'ho provato con successo su g ++ e ha dato l'errore:

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

Interessante. :-)


Sono d'accordo che le macro possono essere pericolose, ma quel primo esempio sarebbe abbastanza ovvio per il debug e ovviamente dà solo un errore. Perché ti aspetteresti di più? Ho visto errori molto più brutti come risultato delle macro ...
Jon Cage

È vero che la differenza tra una soluzione e l'altra è quasi banale. Ma in questo caso, poiché stiamo parlando di due stili di codifica concorrenti, allora anche il banale non può essere ignorato, perché dopo ciò, tutto ciò che rimane è il gusto personale (ea quel punto, credo che non dovrebbe essere normalizzato )
paercebal

4

Non è affatto una questione di stile. Anche la domanda è purtroppo sbagliata. Non è possibile confrontare queste direttive del preprocessore nel senso di migliore o più sicuro.

#ifdef macro

significa "se la macro è definita" o "se la macro esiste". Il valore della macro non ha importanza qui. Può essere qualunque cosa.

#if macro

se confronta sempre con un valore. Nell'esempio sopra è il confronto implicito standard:

#if macro !=0

esempio per l'utilizzo di #if

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

ora puoi inserire la definizione di CFLAG_EDITION nel tuo codice

#define CFLAG_EDITION 1 

oppure puoi impostare la macro come flag del compilatore. Vedi anche qui .


2

Il primo mi sembra più chiaro. Mi sembra più naturale farne una bandiera rispetto a definito / non definito.


2

Entrambi sono esattamente equivalenti. Nell'uso idiomatico, #ifdef è usato solo per verificare la definizione (e cosa userei nel tuo esempio), mentre #if è usato in espressioni più complesse, come #if defined (A) &&! Defined (B).


1
L'OP non chiedeva quale fosse il migliore tra "#ifdef" e "#if defined" ma piuttosto tra "# ifdef / # se definito" e "#if".
gambo

1

Un po 'OT, ma attivare / disattivare la registrazione con il preprocessore è decisamente subottimale in C ++. Esistono ottimi strumenti di registrazione come log4cxx di Apache che sono open source e non limitano il modo in cui distribuisci la tua applicazione. Consentono inoltre di modificare i livelli di registrazione senza ricompilazione, hanno un sovraccarico molto basso se si disattiva la registrazione e offrono la possibilità di disattivare completamente la registrazione in produzione.


1
Sono d'accordo, e in realtà lo facciamo nel nostro codice, volevo solo un esempio di qualcosa per cui potresti usare #if ecc. Per
Jon Cage,

1

Ho usato per usare #ifdef , ma quando sono passato a Doxygen per la documentazione, ho scoperto che le macro commentate non possono essere documentate (o, almeno, Doxygen produce un avviso). Ciò significa che non posso documentare le macro di cambio di funzionalità che non sono attualmente abilitate.

Sebbene sia possibile definire le macro solo per Doxygen, ciò significa che verranno documentate anche le macro nelle parti non attive del codice. Personalmente desidero mostrare le opzioni delle funzionalità e altrimenti documentare solo ciò che è attualmente selezionato. Inoltre, rende il codice piuttosto disordinato se ci sono molte macro che devono essere definite solo quando Doxygen elabora il file.

Pertanto, in questo caso, è meglio definire sempre le macro e utilizzare #if.


0

Ho sempre usato #ifdef e flag del compilatore per definirlo ...


Qualche motivo particolare (per curiosità)?
Jon Cage

2
Ad essere onesto non ci ho mai pensato, solo come è stato fatto nei posti in cui ho lavorato. Offre il vantaggio che invece di apportare una modifica al codice per una build di produzione, tutto ciò che devi fare è 'make DEBUG' per il debug o 'make PRODUCTION' per regolare
tloach

0

In alternativa, puoi dichiarare una costante globale e utilizzare il C ++ if, invece del preprocessore #if. Il compilatore dovrebbe ottimizzare per te i rami inutilizzati e il tuo codice sarà più pulito.

Ecco cosa dice C ++ Gotchas di Stephen C. Dewhurst sull'uso di # if.


1
Questa è una pessima soluzione, ha i seguenti problemi: 1. Funziona solo nelle funzioni, non puoi rimuovere le variabili di classe non necessarie, ecc. 2. I compilatori possono lanciare avvertimenti sul codice non raggiungibile 3. Il codice in if deve ancora essere compilato, il che significa devi mantenere definite tutte le tue funzioni di debug, ecc.
Don Neufeld,

In primo luogo la domanda riguardava specificamente il debug printfs, quindi le variabili di classe non necessarie non sono un problema qui. Secondo, date le capacità dei compilatori moderni, dovresti usare #ifdefs il meno possibile. Nella maggior parte dei casi è possibile utilizzare invece configurazioni di build o specializzazioni di template.
Dima

0

C'è una differenza in caso di modo diverso per specificare una definizione condizionale al driver:

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

produzione:

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

Ciò significa che -DAè sinonimo di -DA=1e se il valore viene omesso, potrebbe causare problemi in caso di #if Autilizzo.


0

Mi piace #define DEBUG_ENABLED (0)quando potresti volere più livelli di debug. Per esempio:

#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

Rende più facile il debug delle perdite di memoria, senza avere tutte quelle righe di registro nel tuo modo di eseguire il debug di altre cose.

Inoltre, #ifndefintorno alla definizione è più semplice scegliere un livello di debug specifico dalla riga di comando:

make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc

Se non fosse per questo, darei vantaggio a #ifdef perché il flag compilatore / make verrebbe sovrascritto da quello nel file. Quindi non devi preoccuparti di modificare nuovamente l'intestazione prima di eseguire il commit.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.