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

StackOverflow https://stackoverflow.com/questions/135069

Domanda

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

Fondamentalmente, abbiamo alcune dichiarazioni 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 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.

È stato utile?

Soluzione

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

Innanzitutto, puoi utilizzare DEBUG_ENABLED nei test compilati e del preprocessore. Esempio: spesso voglio timeout più lunghi quando il debug è abilitato, quindi usando #if , posso 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 desideri migrare da un #define a una costante globale. I #define sono generalmente disapprovati dalla maggior parte dei programmatori C ++.

In terzo luogo, dici di avere una divisione nella tua squadra. Suppongo 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 utilizza #ifdef verrà 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 punto di leggibilità minore. 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 in cui non hai bisogno di parentesi.

#define DEBUG_ENABLED true

anziché

#define DEBUG_ENABLED (1)

Altri suggerimenti

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 è necessario il codice di debug, inserirlo in D (); . E il tuo programma non è inquinato da labirinti orribili di #ifdef .

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

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

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 header in file.cpp:

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

Quindi hai un problema, il compilatore interpreta COOL_FEATURE come 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 caratteristiche, quindi non sarebbe portatile.

Abbiamo adottato un modo portatile per correggere questo caso e testare lo stato di una caratteristica: macro di funzione.

se hai modificato la funzione sopra.h in:

#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 header in file.cpp:

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

Il preprocessore si sarebbe sbagliato a causa dell'utilizzo di una macro di funzione non definita.

Ai fini dell'esecuzione della compilazione condizionale, #if e #ifdef sono quasi uguali, ma non del tutto. Se la tua compilazione condizionale dipende da due simboli, #ifdef non funzionerà altrettanto. Ad esempio, supponiamo 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

Usando #ifdef quanto sopra diventa molto più complicato, specialmente facendo funzionare la parte #else.

Lavoro sul codice che utilizza ampiamente la compilazione condizionale e abbiamo una combinazione di #if & amp; #ifdef. Tendiamo a usare # ifdef / # ifndef per il caso semplice e #if ogni volta che due o più simboli vengono valutati.

Penso che sia interamente una questione di stile. Nessuno dei due ha davvero un ovvio vantaggio rispetto all'altro.

La coerenza è più importante di entrambe le scelte particolari, quindi ti consiglio di stare insieme con il tuo team e scegliere uno stile e attenersi ad esso.

Io stesso preferisco:

#if defined(DEBUG_ENABLED)

Poiché semplifica la creazione di codice che cerca la condizione opposta molto più facile da individuare:

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)

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

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

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

Lo fai 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 codice non verrà confuso con le direttive del preprocessore.

Se viene visualizzato l'avviso "L'espressione non ha alcun effetto" e vuoi sbarazzartene, 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);

#if ti dà la possibilità di impostarlo su 0 per disattivare la funzionalità, pur rilevando che l'interruttore è lì.
Personalmente ho sempre #define DEBUG 1 in modo da poterlo catturare con #if o #ifdef

#if e #define MY_MACRO (0)

L'uso di #if significa che hai creato un " define " macro, ovvero qualcosa che verrà cercato nel codice per essere sostituito da "(0)". Questo è il "macro inferno" Odio vedere in C ++, perché inquina il codice con potenziali modifiche al codice.

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

fornisce il seguente errore su g ++:

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

Solo un errore .

Il che 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 personale con le macro che giocano in silenzio con il mio codice non è piena di gioia e pieno successo, quindi ...

#ifdef e #define MY_MACRO

L'uso di #ifdef significa che "definisci " qualcosa. Non che tu gli dia un valore. È ancora inquinante, ma almeno non verrà sostituito da nulla e non sarà visto dal codice C ++ come un'istruzione di codice lagitimate. Lo stesso codice sopra, con una semplice definizione, è:

#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 i seguenti avvisi:

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

...

Conclusione

Preferirei vivere senza macro nel mio codice, ma per diversi motivi (definizione delle protezioni delle 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, dando loro nomi così lunghi e così alieni che nessuno nella sua mente corretta userà "per caso" e che 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 un valore che non sarà mai corretto C ++ da aggiungere alla mia definizione. Qualcosa come

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

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

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

Interessante. : -)

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

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) & amp; & amp; ! Definito (B).

Un po 'OT, ma l'attivazione / disattivazione della registrazione con il preprocessore è decisamente non ottimale in C ++. Esistono strumenti di registrazione gradevoli come log4cxx di Apache che sono open-source e non limitano come distribuisci la tua applicazione. Consentono inoltre di modificare i livelli di registrazione senza ricompilazione, hanno un sovraccarico molto basso se la disattivi e ti danno la possibilità di disattivare completamente la registrazione in produzione.

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

#ifdef macro

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

#if macro

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

#if macro !=0

esempio per l'uso 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. Anche vedi qui .

Prima usavo #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 avvertimento). Questo significa che non posso documentare le macro di cambio funzionalità che non sono attualmente abilitate.

Sebbene sia possibile definire le macro solo per Doxygen, ciò significa che anche le macro nelle parti non attive del codice saranno documentate. Personalmente, voglio mostrare le opzioni 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 .

Ho sempre usato #ifdef e flag di compilazione per definirlo ...

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

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

Esiste una differenza nel caso di un modo diverso di specificare una definizione condizionale per il driver:

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

uscita:

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

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top