Je ne sais pas quand utiliser des macros ou des énumérations. Les deux peuvent être utilisés comme constantes, mais quelle est la différence entre eux et quel est l'avantage de l'un ou de l'autre? Est-ce lié d'une manière ou d'une autre au niveau du compilateur?
En termes de lisibilité, les énumérations font de meilleures constantes que les macros, car les valeurs associées sont regroupées. De plus, enum
définit un nouveau type, donc les lecteurs de votre programme auraient plus de facilité à déterminer ce qui peut être passé au paramètre correspondant.
Comparer
#define UNKNOWN 0
#define SUNDAY 1
#define MONDAY 2
#define TUESDAY 3
...
#define SATURDAY 7
à
typedef enum {
UNKNOWN
, SUNDAY
, MONDAY
, TUESDAY
, ...
, SATURDAY
} Weekday;
Il est beaucoup plus facile de lire du code comme celui-ci
void calendar_set_weekday(Weekday wd);
que ça
void calendar_set_weekday(int wd);
parce que vous savez quelles constantes il est correct de passer.
Une macro est une chose de préprocesseur et le code compilé n'a aucune idée des identifiants que vous créez. Ils ont déjà été remplacés par le préprocesseur avant que le code ne frappe le compilateur. Une énumération est une entité de compilation et le code compilé conserve des informations complètes sur le symbole, qui sont disponibles dans le débogueur (et d'autres outils).
Préférez les énumérations (quand vous le pouvez).
En C, il est préférable d'utiliser des énumérations pour les énumérations réelles: lorsqu'une variable peut contenir l'une des multiples valeurs auxquelles on peut donner des noms. L'un des avantages des énumérations est que le compilateur peut effectuer des vérifications au-delà de ce que le langage requiert, comme le fait qu'une instruction switch sur le type d'énumération ne manque aucun des cas. Les identificateurs d'énumération se propagent également dans les informations de débogage. Dans un débogueur, vous pouvez voir le nom de l'identificateur comme la valeur d'une variable enum, plutôt que simplement la valeur numérique.
Les énumérations peuvent être utilisées uniquement pour l'effet secondaire de la création de constantes symboliques de type intégral. Par exemple:
enum { buffer_size = 4096 }; /* we don't care about the type */
cette pratique n'est pas très répandue. Pour une chose, buffer_size
sera utilisé comme un entier et non comme un type énuméré. Un débogueur ne rendra pas 4096
en buffer_size
, car cette valeur ne sera pas représentée comme le type énuméré. Si vous déclarez un char array[max_buffer_size];
puis sizeof array
n'apparaîtra pas comme buffer_size
. Dans cette situation, la constante d'énumération disparaît au moment de la compilation, il peut donc aussi bien s'agir d'une macro. Et il y a des inconvénients, comme ne pas pouvoir contrôler son type exact. (Il peut y avoir un petit avantage dans certaines situations où la sortie des étapes de prétraitement de la traduction est capturée sous forme de texte. Une macro se sera transformée en 4096, tandis que buffer_size
restera comme buffer_size
).
Un symbole de préprocesseur nous permet de faire ceci:
#define buffer_size 0L /* buffer_size is a long int */
Notez que diverses valeurs de C's <limits.h>
comme UINT_MAX
sont des symboles de préprocesseur et non des symboles d'énumération, pour de bonnes raisons, car ces identifiants doivent avoir un type déterminé avec précision. Un autre avantage d'un symbole de préprocesseur est que nous pouvons tester sa présence, ou même prendre des décisions en fonction de sa valeur:
#if ULONG_MAX > UINT_MAX
/* unsigned long is wider than unsigned int */
#endif
Bien sûr, nous pouvons également tester les constantes énumérées, mais pas de manière à pouvoir modifier les déclarations globales en fonction du résultat.
Les énumérations sont également mal adaptées aux masques à bit:
enum modem_control { mc_dsr = 0x1, mc_dtr = 0x2, mc_rts = 0x4, ... }
cela n'a tout simplement pas de sens car lorsque les valeurs sont combinées avec un OU au niveau du bit, elles produisent une valeur qui est en dehors du type. Un tel code provoque également des maux de tête s'il est jamais porté sur C++, qui a (un peu plus) des énumérations de type sécurisé.
Notez qu'il existe des différences entre les macros et les énumérations, et l'une ou l'autre de ces propriétés peut les rendre (non) appropriées en tant que constante particulière.
sizeof(int)
. Pour les tableaux de petites valeurs (jusqu'à, CHAR_MAX
) vous voudrez peut-être un char foo[]
plutôt qu'un enum foo[]
tableau.enum funny_number { PI=3.14, E=2.71 }
.Si la macro est correctement implémentée (c'est-à-dire qu'elle ne souffre pas de problèmes d'associativité lorsqu'elle est substituée), il n'y a pas beaucoup de différence d'applicabilité entre les constantes macro et énum dans les situations où les deux sont applicables, c'est-à-dire dans la situation où vous avez besoin les constantes entières signées spécifiquement.
Cependant, dans le cas général, les macros offrent des fonctionnalités beaucoup plus flexibles. Les énumérations imposent un type spécifique à vos constantes: elles auront le type int
(ou, éventuellement, un type entier signé plus grand), et elles seront toujours signées. Avec les macros, vous pouvez utiliser une syntaxe constante, des suffixes et/ou des conversions de types explicites pour produire une constante de tout type.
Les énumérations fonctionnent mieux lorsque vous avez un groupe de constantes entières séquentielles étroitement associées. Ils fonctionnent particulièrement bien lorsque vous ne vous souciez pas du tout des valeurs réelles des constantes, c'est-à-dire lorsque vous ne vous souciez que d'avoir certains des valeurs uniques bien comportées. Dans tous les autres cas, les macros sont un meilleur choix (ou fondamentalement le seul choix).
En pratique, il y a peu de différence. Ils sont également utilisables comme constantes dans vos programmes. Certains peuvent préférer l'un ou l'autre pour des raisons stylistiques, mais je ne vois aucune raison technique de préférer l'un à l'autre.
Une différence est que les macros vous permettent de contrôler le type intégral des constantes associées. Mais un enum
utilisera un int
.
#define X 100L
enum { Y = 100L };
printf("%ld\n", X);
printf("%d\n", Y); /* Y has int type */