web-dev-qa-db-fra.com

#ifdef vs #if - qui est mieux/plus sûr comme méthode pour activer/désactiver la compilation de sections de code particulières

C'est peut-être une question de style, mais notre équipe de développement est un peu divisée et je me demandais si quelqu'un d'autre avait des idées à ce sujet ...

Fondamentalement, certaines instructions d’impression de débogage sont désactivées au cours d’un 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 car il y a toujours quelque chose de défini et il n'y a aucun danger qu'il puisse détruire d'autres définis ailleurs.

107
Jon Cage

Ma réaction initiale a été#ifdef, bien sûr , mais je pense que #if présente en fait des avantages importants pour cela - voici pourquoi:

Tout d’abord, vous pouvez utiliser DEBUG_ENABLED dans les tests préprocesseurs et compilés. Exemple - Souvent, je souhaite des délais plus longs lorsque le débogage est activé. Par conséquent, 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 dans une meilleure position si vous souhaitez migrer d'un #define vers une constante globale. Les #defines sont généralement mal vus par la plupart des programmeurs C++.

Et, troisièmement, vous dites que votre équipe est divisée. À mon avis, cela signifie que différents membres ont déjà adopté différentes approches et que vous devez normaliser. Décider que #if est le choix préféré signifie que le code utilisant #ifdef compilera et exécutera même lorsque DEBUG_ENABLED est faux. Et il est beaucoup facile de localiser et de supprimer la sortie de débogage qui est produite alors que cela ne devrait pas être que vice-versa.

Oh, et un point de lisibilité mineur. 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. 

#define DEBUG_ENABLED true

au lieu de 

#define DEBUG_ENABLED (1)
78
Roddy

Ils sont tous 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 dans D();. Et votre programme n'est pas pollué par des labyrinthes hideux de #ifdef.

54
R..

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

#define FOO 0

puis

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

Nous avons eu le même problème dans plusieurs fichiers et il y a toujours un problème avec les personnes qui oublient d'inclure un fichier "indicateur de fonctionnalités" (avec une base de code de plus de 41 000 fichiers, il 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 comme étant non dé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 du code tiers définit ou ne définit pas les fonctionnalités, ce qui ne serait donc pas portable.

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

si vous avez modifié la fonctionnalité ci-dessus.h en:

#ifndef FEATURE_H
#define FEATURE_H

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

#endif // FEATURE_H

Mais vous avez encore une fois 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 égaré à cause de l'utilisation d'une macro de fonction non définie. 

18
Brent Priddy

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

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

L’utilisation de #ifdef devient plus compliquée, en particulier l’utilisation de la #else pour fonctionner. 

Je travaille sur du code qui utilise intensivement 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 ou plusieurs symboles sont en cours d'évaluation. 

15
Mike Thompson

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

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

14
Derek Park

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

#if defined(DEBUG_ENABLED)

Comme il est plus facile de créer un code qui recherche la condition opposée, il est beaucoup plus facile de le détecter:

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)
7
Jim Buck

C'est une question de style. Mais je recommande une méthode plus concise:

#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.) Ainsi, votre code ne sera pas corrompu par des directives de pré-processeur.

Si vous obtenez l'avertissement "expression n'a pas d'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);
6
Lev

#if vous donne la possibilité de le mettre à 0 pour désactiver la fonctionnalité, tout en détectant que le commutateur est présent.
Personnellement, j'ai toujours #define DEBUG 1 pour pouvoir l'attraper avec un #if ou un #ifdef

4
Martin Beckett

#if et #define MY_MACRO (0)

Utiliser #if signifie que vous avez créé une macro "définir", c’est-à-dire quelque chose qui sera recherché dans le code à remplacer par "(0)". C’est le "macro enfer" que je déteste voir en C++, car il pollue le code avec des modifications potentielles.

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

Seulement un erreur.

Ce qui signifie que votre macro a bien interagi avec votre code C++: L'appel à la fonction a abouti. Dans ce cas simple, c'est amusant. Mais ma propre expérience avec les macros jouant en silence avec mon code n’est pas pleine de joie et de réalisation, alors ...

#ifdef et #define MY_MACRO

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

#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éfinir des gardes d'en-tête ou des 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, en utilisant #ifdef et #ifndef (ou même #if défini comme suggéré par Jim Buck), et surtout, en leur donnant des noms si longs et si étranges que personne dans son bon esprit n'utilisera "par hasard", cela n'affectera en aucun cas le code C++ légitime.

Post Scriptum

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

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

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

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

Intéressant.:-)

3
paercebal

Un petit OT, mais activer/désactiver la journalisation avec le préprocesseur est définitivement sous-optimal en C++. Il existe de jolis outils de journalisation, tels que log4cxx d'Apache, qui sont à code source ouvert et ne limitent pas la distribution de votre application. Ils vous permettent également de modifier les niveaux de journalisation sans recompilation, ont des frais généraux très faibles si vous désactivez la journalisation et vous permettent de désactiver complètement la journalisation en production.

2
David Nehme

Ce n’est pas une question de style. Aussi la question est malheureusement fausse. Vous ne pouvez pas comparer ces directives de pré-processeur dans le sens de mieux ou plus sûr.

#ifdef macro

signifie "si la macro est définie" ou "si la macro existe". La valeur de la macro n'a pas d'importance ici. Ce 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 soit mettre la définition de CFLAG_EDITION soit dans votre code

#define CFLAG_EDITION 1 

ou vous pouvez définir la macro comme indicateur de compilateur. Aussi voir ici .

2
tmanthey

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

2
axblount

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 define (A) &&! Defin (B).

2
zvrba

J'aime #define DEBUG_ENABLED (0) lorsque vous souhaitez 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

Il est plus facile de déboguer les fuites de mémoire sans avoir à mettre toutes ces lignes de journal dans le processus de débogage.

De plus, le #ifndef autour 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 un avantage à #ifdef parce que l'indicateur de compilation/make serait remplacé par celui du fichier. Ainsi, vous n'avez pas à vous soucier de changer l'en-tête avant de faire la validation.

0
memtha

Il y a 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 - )

sortie:

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

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

0
Orient

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

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

0
Dima

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

0
tloach

J'avais l'habitude d'utiliser #ifdef, mais lorsque je suis passé à Doxygen pour la documentation, j'ai constaté que les macros commentées ne pouvaient pas être documentées (ou, au moins, Doxygen produisait un avertissement). Cela signifie que je ne peux pas documenter les macros de sélecteurs de fonctions qui ne sont pas actuellement activés.

Bien qu'il soit possible de définir les macros uniquement pour Doxygen, cela signifie que les macros des parties non actives du code seront également documentées. Personnellement, je veux montrer les commutateurs de fonctions et sinon ne documenter que ce qui est actuellement sélectionné. En outre, le code est assez compliqué s'il existe de nombreuses macros à définir uniquement lorsque Doxygen traite le fichier.

Par conséquent, dans ce cas, il est préférable de toujours définir les macros et d'utiliser #if.

0
StefanB