OVH Cloud OVH Cloud

Différence entre #define et enum

39 réponses
Avatar
Vincent Belaïche
Bonjour,

Dans la Foire à Questions du groupe on trouve
http://www.levenez.com/lang/c/faq/fclc006.html#q_8

6.8 Quelle est la différence entre une énumération et des #define ?

Il y a peu de différences.

L'un des avantages de l'énumération est que les valeurs numériques sont
assignées automatiquement. De plus, une énumération se manipule comme un
type de données. Certains programmeurs reprochent aux énumérations de
réduire le contrôle qu'ils ont sur la taille des variables de type
énumération.


Il me semble qu'il y a d'autres différences notables:

1) avec les #define on peut tester une valeur entière pendant le
prétraitement

#define TOTO 1

#if TOTO == 1
...
#elif TOTO == 2
...
#else
#endif

(même si le code ci-dessus peut ne pas plaire pour diverse raison, c'est
très utilisé)
Le code ci-dessus ne marche pas si TOTO est un enum

2) Les enum sont en général dispo sous forme symbolique dans un
débogueur qui se respecte, pas les #define

3) Par rapport au commentaire sur la taille non controllée avec les
énum, il me semble qu'on peut qualifier un enum avec `: taille_en_bit'
au sein d'un typedef, un peu comme les champ d'une structure. À
vérifier, et aussi à quelle norme ça répond.

Vincent.

10 réponses

1 2 3 4
Avatar
Antoine Leca
Jean-Marc Bourguet écrivit :
Il y a trois classes de noms en C:


xxxxx cinq (six avec cpp)
- pour les macros (et constantes manifestes) du préprocesseur,
qui de plus écrasent tout sur leur passage
- pour les tags (enum, union, struct)
- pour les labels
- pour les autres choses


- pour les membres d'une déclaration structurée (enum, etc.)
- pour les noms des paramètres dans les déclarations de prototypes

et donc dans une portée donnée un identificateur peut désigner quelque
chose pour chacune des classes.


(sauf les symboles du préprocesseur)


Ce qui ne change rien à la pertinence du commentaire de Jean-Marc.


Antoine
Avatar
Jean-Marc Bourguet
Antoine Leca writes:

Jean-Marc Bourguet écrivit :
Il y a trois classes de noms en C:


xxxxx cinq (six avec cpp)
- pour les macros (et constantes manifestes) du préprocesseur,
qui de plus écrasent tout sur leur passage



OK mais le préprocesseur est tellement à part.

- pour les tags (enum, union, struct)
- pour les labels
- pour les autres choses


- pour les membres d'une déclaration structurée (enum, etc.)
- pour les noms des paramètres dans les déclarations de prototypes



Plus délicat. J'ai tendance à considérer ces contextes comme des portées
(scope en anglais). (Je n'ai pas le temps d'aller voir comment c'est fait
formellement en C; je suis quasiment sûr que ce sont des portées pour le
C++. Et commencer à parler de cela mais fait me poser des questions au
sujet des tags...)

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Lucas Levrel
Le 7 avril 2010, Samuel DEVULDER a écrit :

int fenetre_3_jours(t_jour j1, t_jour j2) {
int delta = j1 - j2;
return -3<delta && delta<3;
}

Que donne fenetre_3_jours(LUNDI, MARDI) compilé sur machine HP 32bits ou
64bits?

Si on s'interdit la soustraction des enums, l'écriture de fenetre_3_jours
sera plus laborieuse (et peut être bugée):

return (j1==LUNDI && (j2<=MERCREDI || j2>=SAMEDI)) ||
(j1==MARDI && (j2<=JEUDI || j2==DIMANCHE)) ||
(j1==MERCREDI && (j2<=VENDREDI)) ||
(j1==JEUDI && (j2>=MARDI && j2<=SAMEDI)) ||
(j1==VENDREDI && (j2>=MERCREDI)) ||
(j1==SAMEDI && (j2>=JEUDI || j2==LUNDI) ||
(j1==DIMANCHE && (j2>=VENDREDI || j2<=MARDI);



Si je ne m'abuse la solution standard *et* lisible c'est
int delta=(j1<j2 ? j2-j1 : j1-j2);

--
LL
Avatar
Manuel Pégourié-Gonnard
Vincent Belaïche scripsit :

en effet, maintenant ça me revient, il s'agissait de spécifier le type
sousjacent et non d'un spécificateur en nombre de bits, mais c'était
fait sans C++0X de la façon suivante (attention j'ignore si ce code est
correct ou non, c'est qq chose que j'ai vu faire):

------------------------
enum couleurs {
rouge, vert, bleu
};
typedef char couleurs;
------------------------



Juste pour être sûr : ce code est valide en C, mais pas en C++, non ?

--
Manuel Pégourié-Gonnard Institut de mathématiques de Jussieu
http://weblog.elzevir.fr/ http://people.math.jussieu.fr/~mpg/
Avatar
Antoine Leca
Jean-Marc Bourguet a écrit :
- pour les tags (enum, union, struct)
- pour les labels
- pour les autres choses



- pour les membres d'une déclaration structurée (enum, etc.)
- pour les noms des paramètres dans les déclarations de prototypes



Plus délicat. J'ai tendance à considérer ces contextes comme des portées
(scope en anglais).



La norme les pose au même niveau dans l'introduction sur la taxinomie
(6.2.3). Et de fait, avec un seul lexème de contexte tu peux déterminer
de quoi il s'agit :
- avant un tag, tu auras struct, union ou enum, et vice-versa
- un (sélecteur de) membre vient derrière . ou ->, et vice-versa
- un label sera, ou après un goto, ou avant : (et les règles sont plus
complexes, ce qui est cohérent avec le fait que la portée des labels
soit elle aussi un concept parallèle aux autres portées)
- et sinon c'est un tout-venant.

En ce qui concerne les noms de paramètres, c'est une bourde de ma part
[rouge]. En fait il s'agit d'une portée différente, et au niveau
syntaxique uniquement, il n'y a pas parallélisme comme pour les macros
ou les labels.


Antoine
Avatar
Antoine Leca
Lucas Levrel a écrit :
Le 7 avril 2010, Samuel DEVULDER a écrit :
Si on s'interdit la soustraction des enums,




^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Si je ne m'abuse la solution standard *et* lisible c'est
int delta=(j1<j2 ? j2-j1 : j1-j2);



?

Antoine
Avatar
Jean-Marc Bourguet
Manuel Pégourié-Gonnard writes:

Vincent Belaïche scripsit :

en effet, maintenant ça me revient, il s'agissait de spécifier le type
sousjacent et non d'un spécificateur en nombre de bits, mais c'était
fait sans C++0X de la façon suivante (attention j'ignore si ce code est
correct ou non, c'est qq chose que j'ai vu faire):

------------------------
enum couleurs {
rouge, vert, bleu
};
typedef char couleurs;
------------------------



Juste pour être sûr : ce code est valide en C, mais pas en C++, non ?



Exact. En gros en C++ il y un typedef automatique

typedef struct/union/enum t t;

avec des exceptions pour autoriser

struct stat;

int stat();

mais la forme ci-dessus ne relève pas des exceptions.

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Samuel DEVULDER
Lucas Levrel a écrit :
Le 7 avril 2010, Samuel DEVULDER a écrit :

int fenetre_3_jours(t_jour j1, t_jour j2) {
int delta = j1 - j2;
return -3<delta && delta<3;
}




Si je ne m'abuse la solution standard *et* lisible c'est
int delta=(j1<j2 ? j2-j1 : j1-j2);


avec:
return delta<3 || delta>4;
(pour une semaine modulo 7)

Oui, il faut faire comme tu l'indiques si on veut éviter le débordement
de signe (et son UB je suppose) en cas d'unsigned. C'est le pb du
absdiff() dont faisait référence Marc Espie dans "Re: Implementation
strlen()" je crois.

Mais qu'est ce qui dit que l'enum est unsigned? Mystère!

On peut être du coup tenté de dire que la bonne façon d'utiliser les
enums, c'est de dire que ce ne sont pas des nombre qu'on peut
soustraire, additionner, (multiplier?) avec une arithmétique (et des
soucis signed/unsigned pas clairs), mais uniquement des "symboles",
qu'on peut ordonner avec "<" ou "==".

En meme temps l'ordre avec "<" se discute car il ne reflète pas celui de
la déclaration (quid si on avait écrit MERCREDI=-15 en plein milieu?)
Par contre "==" me semble marcher à tous les coups, mais produit un truc
assez moche qui revient à énumérer tous les couples de jours.

Pour éviter tout cela il faudrait en fait écrire une fonction de
conversion entre enum->int, qui respecte le "<" malgrès la présence d'un
MERCREDI=-15, et qui soit signed pour pouvoir calculer la distance entre
2 jours facilement:

int jour2int(t_jour j) {
int n = -1;
switch(j) {
case DIMANCHE: ++n; /* fall through */
case SAMEDI: ++n; /* fall through */
case VENDREDI: ++n; /* fall through */
case JEUDI: ++n; /* fall through */
case MERCREDI: ++n; /* fall through */
case MARDI: ++n; /* fall through */
case LUNDI: ++n; /* fall through */
}
return n;
}
(nota: ici le fall through, c'est juste pour varier les plaisirs. On
aurait pu banalement écrire return 2, 3 ou 4.)

Résultat = abs(jour2int(j1) - jour2int(j2))<3;

sam.
Avatar
Jean-Marc Bourguet
Antoine Leca writes:

Jean-Marc Bourguet a écrit :
- pour les tags (enum, union, struct)
- pour les labels
- pour les autres choses







- pour les membres d'une déclaration structurée (enum, etc.)



Plus délicat. J'ai tendance à considérer ces contextes comme des portées
(scope en anglais).



La norme les pose au même niveau dans l'introduction sur la taxinomie
(6.2.3). Et de fait, avec un seul lexème de contexte tu peux déterminer
de quoi il s'agit :
- avant un tag, tu auras struct, union ou enum, et vice-versa
- un (sélecteur de) membre vient derrière . ou ->, et vice-versa
- un label sera, ou après un goto, ou avant : (et les règles sont plus
complexes, ce qui est cohérent avec le fait que la portée des labels
soit elle aussi un concept parallèle aux autres portées)
- et sinon c'est un tout-venant.



Merci pour la recherche.

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Antoine Leca
Samuel DEVULDER écrivit :
Mais qu'est ce qui dit que l'enum est unsigned? Mystère!



Si tu fabriques des membres d'une énumération qui ont des valeurs plus
grandes que INT_MAX (32767 en 16 bits, cela va vite), de toutes façons
tu es dans le royaume des extensions propriétaires.

Maintenant, si ton implémentation décide de son propre chef de rendre
non signé (retypé en unsigned voire unsigned long) une constante
d'énumération alors même que sa valeur est inférieure à INT_MAX, c'est
une non-conformité en bonne et due forme.


Antoine
1 2 3 4