#ifdef vs #if – qui est mieux / plus sûr comme méthode pour activer / désactiver la compilation de certaines sections de code?

Cela peut être une question de style, mais il y a un peu de division dans notre équipe de développeurs et je me demandais 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 les suivants:

 // #define DEBUG_ENABLED ... SomeFunction() { int someVariable = 5; #ifdef DEBUG_ENABLED printf("Debugging: someVariable == %d", someVariable); #endif } 

… laquelle de ces méthodes vous semble la meilleure et pourquoi? Mon sentiment est que le premier est plus sûr parce qu’il ya toujours quelque chose de défini et qu’il ne risque pas de détruire d’autres définitions ailleurs.

Ma réaction initiale a été #ifdef , bien sûr , mais je pense que #if présente des avantages significatifs – voici pourquoi:

Tout d’abord, vous pouvez utiliser DEBUG_ENABLED dans les tests préprocesseurs et compilés. Exemple – Souvent, je veux des délais 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 constante à une constante globale. #define s sont généralement mal vus par la plupart des programmeurs C ++.

Et, troisièmement, vous dites que vous avez une division dans votre équipe. J’imagine que cela signifie que différents membres ont déjà adopté différentes approches et que vous devez normaliser. Déterminer que #if est le choix préféré signifie que le code utilisant #ifdef comstackra et DEBUG_ENABLED même si DEBUG_ENABLED est faux. Et il est beaucoup plus facile de retrouver et de supprimer les sorties de débogage produites alors qu’elles ne devraient pas l’être, et vice versa.

Oh, et un petit sharepoint lisibilité. Vous devriez être capable d’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) 

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 du code de débogage, placez-le dans D(); . Et votre programme n’est pas pollué par des labyrinthes hideux de #ifdef .

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

Nous avons eu le même problème dans plusieurs fichiers et il y a toujours le problème avec des personnes oubliant d’inclure un fichier “d’indicateur de fonctionnalités” (avec une base de code 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 alors vous avez oublié d’inclure le fichier d’en-tête dans le fichier.cpp:

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

Ensuite, vous avez un problème, le compilateur interprète COOL_FEATURE comme é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 de fonctionnalités, ce qui ne serait pas portable.

Nous avons adopté une méthode portable pour corriger ce cas, ainsi que pour tester l’état d’une fonctionnalité: les macros de fonction.

si vous avez changé la fonctionnalité ci-dessus.h pour:

 #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 aurait été erroné à cause de l’utilisation d’une macro de fonction non définie.

Je pense que c’est 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, alors je vous recommande de vous réunir avec votre équipe, de choisir un style et de vous y tenir.

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 

En utilisant #ifdef, ce qui précède devient beaucoup plus compliqué, en particulier pour que la partie #else fonctionne.

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.

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

 #if defined(DEBUG_ENABLED) 

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

 #if !defined(DEBUG_ENABLED) 

contre.

 #ifndef(DEBUG_ENABLED) 

C’est une question de style. Mais je recommande une façon plus concise de le faire:

 #ifdef USE_DEBUG #define debug_print printf #else #define debug_print #endif debug_print("i=%d\n", i); 

Vous le faites une fois, puis utilisez toujours debug_print () pour imprimer ou ne rien faire. (Oui, cela comstackra dans les deux cas.) Ainsi, votre code ne sera pas altéré par les directives du préprocesseur.

Si vous obtenez l’avertissement “expression n’a aucun effet” et que vous voulez 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); 

#if vous donne la possibilité de le définir sur 0 pour désactiver la fonctionnalité, tout en détectant que le commutateur est présent.
Personnellement, je #define DEBUG 1 toujours #define DEBUG 1 pour que je puisse l’attraper avec un #if ou un #ifdef

#if et #define MY_MACRO (0)

Utiliser #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 le “macro hell” que je déteste voir en C ++, car il pollue le code avec des modifications de code 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 ===| 

Une seule erreur

Ce qui signifie que votre macro a réussi à interagir 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. Pas que vous lui donniez une valeur. Il est toujours polluant, mais au moins, il sera “remplacé par rien” et ne sera pas vu par le code C ++ comme une déclaration de code lagitimate. 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é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 étrangers que personne dans son esprit ne les utilisera. il “par hasard”, et cela n’affectera en aucun cas le code C ++ légitime.

Post Scriptum

Maintenant 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 ++. Quelque chose comme

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

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

 main.cpp|410|error: stray '@' in program| 

Intéressant. 🙂

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

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), tandis que #if est utilisé dans des expressions plus complexes, telles que #if défini (A) &&! Defined (B).

Un peu d’OT, mais activer / désactiver la journalisation avec le préprocesseur est certainement sous-optimal en C ++. Il existe d’ excellents outils de journalisation, tels que log4cxx d’Apache, qui sont open-source et ne limitent pas la manière dont vous dissortingbuez votre application. Ils vous permettent également de modifier les niveaux de journalisation sans recompilation, d’avoir une surcharge très faible si vous désactivez la déconnexion et de vous déconnecter complètement en production.

Ce n’est pas une question de style du tout. Aussi la question est malheureusement mauvaise. Vous ne pouvez pas comparer ces directives de préprocesseur dans le sens de mieux ou de sécurité.

 #ifdef macro 

signifie “si la macro est définie” ou “si la macro existe”. La valeur de macro n’a pas d’importance ici. Ça 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 pour l’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 du compilateur. Voir aussi ici .

J’ai toujours utilisé les indicateurs #ifdef et comstackr pour le définir …

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

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

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 pouvaient pas être documentées (ou du moins, Doxygen produisait un avertissement). Cela signifie que je ne peux pas documenter les macros de commutateur 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 veux montrer les changements de fonctionnalités et documenter uniquement ce qui est actuellement sélectionné. De plus, cela rend le code très désordonné si de nombreuses macros doivent être définies 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 .

Il existe une différence en cas de 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 que si value est omis, cela peut entraîner des problèmes en cas d'utilisation #if A