Pourquoi ne définir une macro que si elle n'est pas déjà définie?

93

Partout dans notre base de code C, je vois chaque macro définie de la manière suivante:

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

#ifndef BEEPTRIM_ROLL_RATE_DEGPS
#define BEEPTRIM_ROLL_RATE_DEGPS                    0.2f
#endif

#ifndef FORCETRIMRELEASE_HOLD_TIME_MS
#define FORCETRIMRELEASE_HOLD_TIME_MS               1000.0f
#endif

#ifndef TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS
#define TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS       50.0f
#endif

Quelle est la justification de ces vérifications de définition au lieu de simplement définir les macros?

#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#define BEEPTRIM_ROLL_RATE_DEGPS                    0.2f
#define FORCETRIMRELEASE_HOLD_TIME_MS               1000.0f
#define TRIMSYSTEM_SHEARPIN_BREAKINGFORCE_LBS       50.0f

Je ne trouve pas cette pratique expliquée nulle part sur le Web.

Trevor Hickey
la source
6
Il est garanti que la modification des constantes ailleurs dans le code fonctionne de cette manière. Si quelqu'un d'autre définit l'une de ces macros, elles ne seront pas écrasées par le préprocesseur lors de l'analyse de ce fichier.
Enzo Ferber le
8
C'est un exemple du principe de conception WET.
dur le
Publié une réponse avec un exemple, essayez de le compiler.
Enzo Ferber le

Réponses:

141

Cela vous permet de remplacer les macros lorsque vous compilez:

gcc -DMACRONAME=value

Les définitions du fichier d'en-tête sont utilisées par défaut.

Barmar
la source
51

Comme je l'ai dit dans le commentaire, imaginez cette situation:

foo.h

#define FOO  4

defs.h

#ifndef FOO
#define FOO 6
#endif

#ifndef BAR
#define BAR 4
#endif

bar.c

#include "foo.h"
#include "defs.h"

#include <stdio.h>

int main(void)
{
    printf("%d%d", FOO, BAR);
    return 0;
}

Imprimera 44.

Cependant, si le conditionnel ifndefn'était pas là, le résultat serait des avertissements de compilation de redéfinition de MACRO et il sera imprimé 64.

$ gcc -o bar bar.c
In file included from bar.c:2:0:
defs.h:1:0: warning: "FOO" redefined [enabled by default]
 #define FOO 6
 ^
In file included from bar.c:1:0:
foo.h:1:0: note: this is the location of the previous definition
 #define FOO 4
 ^
Enzo Ferber
la source
1
Ceci est spécifique au compilateur. La redéfinition d'une macro de type objet est illégale à moins que la redéfinition soit «la même» (il y a une spécification plus technique pour cela, mais ce n'est pas important ici). Le code illégal nécessite un diagnostic et, après avoir émis un diagnostic (ici un avertissement), le compilateur est libre de tout faire, y compris de compiler le code avec des résultats spécifiques à l'implémentation.
Pete Becker
7
Si vous avez des définitions contradictoires pour la même macro, ne préféreriez- vous pas recevoir l'avertissement dans la plupart des cas? Plutôt que d'utiliser silencieusement la première définition (car la 2ème utilise un ifdefpour éviter de redéfinir).
Peter Cordes
@PeterCordes La plupart du temps, les définitions sous #infdefs sont utilisées comme valeurs de «repli» ou «par défaut». Fondamentalement, "si l'utilisateur l'a configuré, très bien. Sinon, utilisons une valeur par défaut."
Angew n'est plus fier de SO
@Angew: Ok, donc si vous en avez #definesdans un en-tête de bibliothèque qui font partie de l'ABI de la bibliothèque, vous ne devriez pas les encapsuler #ifndef. (Ou mieux, utilisez un enum). Je voulais juste préciser que ce #ifndefn'est approprié que lorsque vous avez une définition personnalisée pour quelque chose dans une unité de compilation, mais pas une autre. Si a.cinclut les en-têtes dans un ordre différent de celui b.c, ils peuvent obtenir des définitions différentes de max(a,b), et l'une de ces définitions peut rompre avec max(i++, x), mais l'autre peut utiliser des temporaires dans une expression-instruction GNU. Toujours déroutant au moins!
Peter Cordes
@PeterCordes Ce que j'aime faire dans ce cas est#ifdef FOO #error FOO already defined! #endif #define FOO x
Cole Johnson
17

Je ne connais pas le contexte, mais cela peut être utilisé pour donner à l'utilisateur la possibilité de remplacer les valeurs définies par ces définitions de macro. Si l'utilisateur définit explicitement une valeur différente pour l'une de ces macros, elle sera utilisée à la place des valeurs utilisées ici.

Par exemple, dans g ++, vous pouvez utiliser l' -Dindicateur lors de la compilation pour passer une valeur à une macro.

Ivaylo Strandjev
la source
14

Ceci est fait pour que l'utilisateur du fichier d'en-tête puisse remplacer les définitions de son code ou de l'indicateur -D du compilateur.


la source
7

Tout projet C réside sur plusieurs fichiers source. Lorsque vous travaillez sur un seul fichier source, les vérifications semblent (et en fait) inutiles, mais lorsque vous travaillez sur un grand projet C, il est recommandé de vérifier les définitions existantes avant de définir une constante. L'idée est simple: vous avez besoin de la constante dans ce fichier source spécifique, mais il se peut qu'elle ait déjà été définie dans un autre.

George
la source
2

Vous pourriez penser à un framework / bibliothèque qui donne à l'utilisateur un préréglage par défaut qui permet à l'utilisateur de le compiler et de travailler dessus. Ces définitions sont réparties dans différents fichiers et l'utilisateur final est invité à inclure son fichier config.h où il peut configurer ses valeurs. Si l'utilisateur en a oublié certains, le système peut continuer à fonctionner grâce au préréglage.

LP
la source
1

En utilisant

#ifndef BEEPTRIM_PITCH_RATE_DEGPS
#define BEEPTRIM_PITCH_RATE_DEGPS                   0.2f
#endif

permet à l'utilisateur de définir la valeur de la macro à l'aide de l'argument de ligne de commande (dans gcc / clang / VS) -DBEEPTRIM_PITCH_RATE_DEGPS=0.3f.

Il y a une autre raison importante. Redéfinir différemment une macro de préprocesseur est une erreur. Voir cette réponse à une autre question SO . Sans la #ifndefvérification, le compilateur doit produire une erreur s'il -DBEEPTRIM_PITCH_RATE_DEGPS=0.3fest utilisé comme argument de ligne de commande dans l'appel du compilateur.

R Sahu
la source