#ifdef vs #if - quelle méthode est la meilleure / la plus sûre pour activer / désactiver la compilation de sections particulières de code?

112

C'est peut-être une question de style, mais il y a un peu de division dans notre équipe de développement et je me suis demandé si quelqu'un d'autre avait des idées à ce sujet ...

Fondamentalement, nous avons des instructions d'impression de débogage que nous désactivons pendant le développement normal. Personnellement, je préfère faire ce qui suit:

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

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
    int someVariable = 5;

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

Certains membres de l'équipe préfèrent cependant ce qui suit:

// #define DEBUG_ENABLED

...

SomeFunction()
{
    int someVariable = 5;

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

... laquelle de ces méthodes vous convient le mieux et pourquoi? Mon sentiment est que le premier est plus sûr parce qu'il y a toujours quelque chose de défini et qu'il n'y a aucun danger que cela puisse détruire d'autres définitions ailleurs.

Jon Cage
la source
Remarque: avec #if, vous pouvez également utiliser #elifde manière cohérente, contrairement à #ifdef. Ainsi, au lieu de simplement utiliser #define BLAH, utilisez #define BLAH 1avec #if BLAH, etc ...
Andrew

Réponses:

82

Ma première réaction a été #ifdef, bien sûr , mais je pense que cela #ifprésente en fait des avantages significatifs - voici pourquoi:

Tout d'abord, vous pouvez utiliser DEBUG_ENABLEDdans le préprocesseur et les tests compilés. Exemple - Souvent, je veux des délais d'expiration plus longs lorsque le débogage est activé, donc en utilisant #if, je peux écrire ceci

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

... au lieu de ...

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

Deuxièmement, vous êtes mieux placé si vous souhaitez migrer d'une #defineconstante à une constante globale. #defines sont généralement mal vus par la plupart des programmeurs C ++.

Et, troisièmement, vous dites que votre équipe est divisée. Je suppose que cela signifie que différents membres ont déjà adopté des approches différentes et que vous devez normaliser. La décision qui #ifest le choix préféré signifie que le code utilisant #ifdefsera compilé - et exécuté - même si la valeur DEBUG_ENABLEDest false. Et il est beaucoup plus facile de retrouver et de supprimer la sortie de débogage qui est produite alors qu'elle ne devrait pas l'être que vice-versa.

Oh, et un petit point de lisibilité. Vous devriez pouvoir utiliser true / false plutôt que 0/1 dans votre #define, et comme la valeur est un seul jeton lexical, c'est la seule fois où vous n'avez pas besoin de parenthèses autour d'elle.

#define DEBUG_ENABLED true

au lieu de

#define DEBUG_ENABLED (1)
Roddy
la source
La constante peut ne pas être utilisée pour activer / désactiver le débogage, donc déclencher un #ifdef avec un #define à 0 pourrait ne pas être si bénin. Quant à vrai / faux, ils ont été ajoutés dans C99 et n'existent pas dans C89 / C90.
Michael Carman
Micheal: Il / elle plaidait contre l'utilisation de #ifdef?!
Jon Cage
7
Ouais, un problème avec #ifdefest que cela fonctionne avec des choses qui ne sont pas définies; qu'ils ne soient pas définis intentionnellement ou à cause d'une faute de frappe ou quoi que ce soit.
bames53
6
Votre ajout à la réponse est faux. #if DEBUG_ENBALEDn'est pas une erreur détectée par le préprocesseur. Si DEBUG_ENBALEDn'est pas défini, il s'étend au jeton 0dans les #ifdirectives.
R .. GitHub STOP HELPING ICE
6
@R .. Dans de nombreux compilateurs, vous pouvez activer un avertissement pour "#if DEBUG_ENABLED" lorsque DEBUG_ENABLED n'est pas défini. Dans GCC, utilisez "-Wundef". Dans Microsoft Visual Studio, utilisez "/ w14668" pour activer C4668 comme avertissement de niveau 1.
Will le
56

Ils sont tous les deux hideux. Au lieu de cela, faites ceci:

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

Ensuite, chaque fois que vous avez besoin de code de débogage, mettez-le à l'intérieur D();. Et votre programme n'est pas pollué par des labyrinthes hideux #ifdef.

R .. GitHub STOP AIDING ICE
la source
6
@MatthieuM. En fait, je pense que la version originale était bien. Le point-virgule serait interprété comme une instruction vide. Cependant, oublier le point-virgule pourrait le rendre dangereux.
Casey Kuball
31

#ifdef vérifie simplement si un jeton est défini, donné

#define FOO 0

puis

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

Nous avons eu le même problème sur plusieurs fichiers et il y a toujours le problème avec les gens qui oublient d'inclure un fichier «indicateur de fonctionnalités» (avec une base de code de> 41 000 fichiers, c'est facile à faire).

Si vous aviez feature.h:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

Mais ensuite, vous avez oublié d'inclure le fichier d'en-tête dans file.cpp:

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

Ensuite, vous avez un problème, le compilateur interprète COOL_FEATURE étant indéfini comme un "faux" dans ce cas et ne parvient pas à inclure le code. Oui, gcc prend en charge un indicateur qui provoque une erreur pour les macros non définies ... mais la plupart des codes tiers définissent ou ne définissent pas les fonctionnalités, donc ce ne serait pas si portable.

Nous avons adopté un moyen portable de corriger ce cas ainsi que de tester l'état d'une fonctionnalité: les macros de fonction.

si vous avez changé le feature.h ci-dessus en:

#ifndef FEATURE_H
#define FEATURE_H

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

#endif // FEATURE_H

Mais vous avez à nouveau oublié d'inclure le fichier d'en-tête dans file.cpp:

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

Le préprocesseur se serait trompé en raison de l'utilisation d'une macro de fonction non définie.

Brent Priddy
la source
17

Pour effectuer une compilation conditionnelle, #if et #ifdef sont presque identiques, mais pas tout à fait. Si votre compilation conditionnelle dépend de deux symboles, #ifdef ne fonctionnera pas aussi bien. Par exemple, supposons que vous ayez deux symboles de compilation conditionnelle, PRO_VERSION et TRIAL_VERSION, vous pourriez avoir quelque chose comme ceci:

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

Utiliser #ifdef ce qui précède devient beaucoup plus compliqué, en particulier pour faire fonctionner la partie #else.

Je travaille sur du code qui utilise beaucoup la compilation conditionnelle et nous avons un mélange de #if & #ifdef. Nous avons tendance à utiliser # ifdef / # ifndef pour le cas simple et #if chaque fois que deux symboles ou plus sont en cours d'évaluation.

Mike Thompson
la source
1
en #if definedquoi est defined-ce un mot clé ou?
nmxprime
15

Je pense que c'est entièrement une question de style. Ni l'un ni l'autre n'a vraiment d'avantage évident sur l'autre.

La cohérence est plus importante que l'un ou l'autre choix particulier, donc je vous recommande de vous réunir avec votre équipe et de choisir un style et de vous y tenir.

Parc Derek
la source
8

Je préfère moi-même:

#if defined(DEBUG_ENABLED)

Comme il est plus facile de créer du code qui recherche la condition opposée beaucoup plus facile à repérer:

#if !defined(DEBUG_ENABLED)

contre.

#ifndef(DEBUG_ENABLED)
Jim Buck
la source
8
Personnellement, je pense qu'il est plus facile de rater ce petit point d'exclamation!
Jon Cage
6
Avec la coloration syntaxique? :) Dans la coloration syntaxique, le "n" dans "ifndef" est beaucoup plus difficile à repérer car il est de la même couleur.
Jim Buck
D'accord, je voulais dire que #ifndef est plus facile à repérer que #if! Défini lorsque vous comparez avec #if défini .. mais étant donné tous les #if définis / # if! Définis vs # ifdef / # ifndef, l'un ou l'autre est également mal lisible!
Jon Cage
@JonCage Je sais que cela fait quelques années depuis ce commentaire, mais je tiens à souligner que vous pouvez l'écrire #if ! definedpour rendre le !plus visible et difficile à manquer.
Pharap
@Pharap - Cela ressemble certainement à une amélioration :)
Jon Cage
7

C'est une question de style. Mais je recommande une manière plus concise de le faire:

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

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

Vous faites cela une fois, puis utilisez toujours debug_print () pour imprimer ou ne rien faire. (Oui, cela compilera dans les deux cas.) De cette façon, votre code ne sera pas brouillé avec les directives du préprocesseur.

Si vous obtenez l'avertissement "expression n'a aucun effet" et que vous souhaitez vous en débarrasser, voici une 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
la source
3
Peut-être que la macro d'impression n'était pas le meilleur exemple après tout - nous le faisons déjà dans notre base de code pour notre code de débogage plus standard. Nous utilisons les bits #if / #ifdefined pour les zones dans lesquelles vous voudrez peut-être activer le débogage supplémentaire.
Jon Cage
5

#ifvous donne la possibilité de le régler sur 0 pour désactiver la fonctionnalité, tout en détectant toujours que le commutateur est là.
Personnellement, je peux toujours l' #define DEBUG 1attraper avec un #if ou un #ifdef

Martin Beckett
la source
1
Cela échoue, car #define DEBUG = 0 ne fonctionnera plus #if mais exécutera #ifdef
tloach le
1
C'est le point, je peux soit supprimer complètement DEBUG, soit simplement le définir sur 0 pour le désactiver.
Martin Beckett le
ça devrait être #define DEBUG 1. Not#define DEBUG=1
Keshava GN
4

#if et #define MY_MACRO (0)

L'utilisation de #if signifie que vous avez créé une macro "define", c'est-à-dire quelque chose qui sera recherché dans le code pour être remplacé par "(0)". C'est «l'enfer des macros» que je déteste voir en C ++, car il pollue le code avec des modifications potentielles du code.

Par exemple:

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

donne l'erreur suivante sur g ++:

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

Une seule erreur.

Ce qui signifie que votre macro a interagi avec succès avec votre code C ++: l'appel à la fonction a réussi. Dans ce cas simple, c'est amusant. Mais ma propre expérience avec des macros jouant en silence avec mon code n'est pas pleine de joie et de plénitude, alors ...

#ifdef et #define MY_MACRO

Utiliser #ifdef signifie que vous «définissez» quelque chose. Non pas que vous lui donniez une valeur. Il est toujours polluant, mais au moins, il sera "remplacé par rien", et non vu par le code C ++ comme une instruction de code retardée. Le même code ci-dessus, avec une définition simple, il:

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

Donne les avertissements suivants:

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

Alors...

Conclusion

Je préfère vivre sans macros dans mon code, mais pour plusieurs raisons (définition de protections d'en-tête ou macros de débogage), je ne peux pas.

Mais au moins, j'aime les rendre le moins interactif possible avec mon code C ++ légitime. Ce qui signifie utiliser #define sans valeur, utiliser #ifdef et #ifndef (ou même #if défini comme suggéré par Jim Buck), et surtout, leur donner des noms si longs et si extraterrestres que personne dans son bon sens n'utilisera il "par hasard", et cela n'affectera en aucun cas le code C ++ légitime.

Post Scriptum

Maintenant, alors que je relis mon article, je me demande si je ne devrais pas essayer de trouver une valeur qui ne sera jamais correcte en C ++ à ajouter à ma définition. Quelque chose comme

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

qui pourrait être utilisé avec #ifdef et #ifndef, mais ne laissez pas le code se compiler s'il est utilisé dans une fonction ... J'ai essayé cela avec succès sur g ++, et cela a donné l'erreur:

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

Intéressant. :-)

Paercebal
la source
Je conviens que les macros peuvent être dangereuses, mais ce premier exemple serait assez évident à déboguer et bien sûr, il ne donne qu'une seule erreur. Pourquoi vous attendriez-vous à plus? J'ai vu des erreurs beaucoup plus méchantes à la suite de macros ...
Jon Cage
Il est vrai que la différence entre une solution et une autre est presque insignifiante. Mais dans ce cas, comme nous parlons de deux styles de codage concurrents, alors même le plus trivial ne peut être ignoré, car après cela, tout ce qui reste est le goût personnel (et à ce stade, je pense qu'il ne devrait pas être normalisé )
paercebal
4

Ce n'est pas du tout une question de style. La question est également malheureusement erronée. Vous ne pouvez pas comparer ces directives de préprocesseur dans le sens de meilleure ou plus sûre.

#ifdef macro

signifie "si la macro est définie" ou "si la macro existe". La valeur de macro n'a pas d'importance ici. Cela peut être n'importe quoi.

#if macro

si toujours comparer à une valeur. Dans l'exemple ci-dessus, il s'agit de la comparaison implicite standard:

#if macro !=0

exemple d'utilisation de #if

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

vous pouvez maintenant mettre la définition de CFLAG_EDITION soit dans votre code

#define CFLAG_EDITION 1 

ou vous pouvez définir la macro comme indicateur du compilateur. Voir aussi ici .

tmanthey
la source
2

Le premier me paraît plus clair. Il semble plus naturel d'en faire un drapeau par rapport à défini / non défini.

axblount
la source
2

Les deux sont exactement équivalents. En utilisation idiomatique, #ifdef est utilisé uniquement pour vérifier la définition (et ce que j'utiliserais dans votre exemple), alors que #if est utilisé dans des expressions plus complexes, telles que #if défini (A) &&! Défini (B).

zvrba
la source
1
L'OP ne demandait pas ce qui est le mieux entre "#ifdef" et "#if défini" mais plutôt entre "# ifdef / # if defined" et "#if".
jarret
1

Un peu OT, mais activer / désactiver la journalisation avec le préprocesseur est définitivement sous-optimal en C ++. Il existe de bons outils de journalisation comme log4cxx d'Apache qui sont open-source et ne limitent pas la façon dont vous distribuez votre application. Ils vous permettent également de modifier les niveaux de journalisation sans recompilation, ont une très faible surcharge si vous désactivez la journalisation et vous donnent la possibilité de désactiver complètement la journalisation en production.

David Nehme
la source
1
Je suis d'accord, et nous faisons cela dans notre code, je voulais juste un exemple de quelque chose que vous pourriez utiliser #if etc. pour
Jon Cage
1

J'avais l'habitude d'utiliser #ifdef , mais quand je suis passé à Doxygen pour la documentation, j'ai trouvé que les macros commentées ne peuvent pas être documentées (ou, du moins, Doxygen produit un avertissement). Cela signifie que je ne peux pas documenter les macros de changement de fonctionnalité qui ne sont pas actuellement activées.

Bien qu'il soit possible de définir les macros uniquement pour Doxygen, cela signifie que les macros dans les parties non actives du code seront également documentées. Personnellement, je souhaite afficher les commutateurs de fonctionnalités et ne documenter que ce qui est actuellement sélectionné. De plus, cela rend le code assez compliqué si de nombreuses macros doivent être définies uniquement lorsque Doxygen traite le fichier.

Par conséquent, dans ce cas, il vaut mieux toujours définir les macros et utiliser #if.

StefanB
la source
0

J'ai toujours utilisé #ifdef et les drapeaux du compilateur pour le définir ...

tloach
la source
Une raison particulière (par curiosité)?
Jon Cage
2
Pour être honnête, je n'y ai jamais pensé - comment ça s'est passé là où j'ai travaillé. Cela donne l'avantage qu'au lieu de faire un changement de code pour une version de production, tout ce que vous avez à faire est de `` faire DEBUG '' pour le débogage, ou de `` faire de la PRODUCTION '' pour la version normale
tloach
0

Vous pouvez également déclarer une constante globale et utiliser le C ++ if, au lieu du préprocesseur #if. Le compilateur devrait optimiser les branches inutilisées pour vous, et votre code sera plus propre.

Voici ce que C ++ Gotchas de Stephen C. Dewhurst dit à propos de l'utilisation des # if.

Dima
la source
1
C'est une mauvaise solution, elle présente les problèmes suivants: 1. Fonctionne uniquement dans les fonctions, vous ne pouvez pas supprimer les variables de classe inutiles, etc. vous devez garder toutes vos fonctions de débogage définies, etc.
Don Neufeld
Tout d'abord, la question portait spécifiquement sur le débogage des printfs, donc les variables de classe inutiles ne sont pas un problème ici. Deuxièmement, étant donné les capacités des compilateurs modernes, vous devez utiliser le moins possible #ifdefs. Dans la plupart des cas, vous pouvez utiliser à la place des configurations de construction ou des spécialisations de modèle.
Dima
0

Il existe une différence dans le cas d'une manière différente de spécifier une définition conditionnelle au pilote:

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

production:

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

Cela signifie que -DAc'est synonyme de -DA=1et si la valeur est omise, cela peut entraîner des problèmes en cas d' #if Autilisation.

Tomilov Anatoliy
la source
0

J'aime #define DEBUG_ENABLED (0)quand vous voudrez peut-être plusieurs niveaux de débogage. Par exemple:

#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

Facilite le débogage des fuites de mémoire, sans avoir toutes ces lignes de journal dans votre manière de déboguer d'autres choses.

De plus, le #ifndefcontour de la définition facilite la sélection d'un niveau de débogage spécifique sur la ligne de commande:

make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc

Sinon, je donnerais l'avantage à #ifdefparce que l'indicateur compilateur / make serait remplacé par celui du fichier. Vous n'avez donc pas à vous soucier de changer l'en-tête avant de faire la validation.

memtha
la source