Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Variable de type enumeration

63 réponses
Avatar
candide
Bonjour,

La norme C90 dit

--------------------------- 8< ------------------------------------
Each enumerated type shall be compatible with an integer type; the
choice of type is implementation-defined.
--------------------------- >8 ------------------------------------

Ce qui n'est pas très clair c'est que par ailleurs :

--------------------------- 8< ------------------------------------
An identifier declared as an enumeration constant has type int.
--------------------------- >8 ------------------------------------

Je me pose donc la question de savoir si on peut utiliser sans risque
une variable de type énumération exactement comme s'il s'agissait d'une
variable de type int ? Je dis "sans risque" parce que formellement, je
crois que rien ne m'en empêche :

--------------------------- 8< ------------------------------------
A char, a short int, or an int bit-field, or their signed or
unsigned varieties, or an object that has enumeration type, may be
used in an expression wherever an int or unsigned int may be used.
--------------------------- >8 ------------------------------------

Si, par exemple

enum toto {A=34, B=51} t;

peut-on écrire

t=28;

(cela pourrait poser problème si l'on considère que les valeurs prises
par une variable de type enum toto sont parmi 34 et 51, cf. ma première
citation de la norme) ? Bon, en fait j'imagine que ça ne pose pas de
problème particulier mais je trouve que la norme n'est pas formelle sur
ce point. Par contre, pourrait-il y avoir des problèmes si on écrivait

t=-5;

?

Quelles précautions particulières prendre avec des variables de type
énumération ? Harbison & Steele recommandent une certaine prudence :


--------------------------- 8< ------------------------------------
We suggest that programmers treat enumerated types as different from
integers and not mix them in integer expressions without using casts.
--------------------------- >8 ------------------------------------

mais ils ne donnent aucun exemple illustrant les précautions à prendre.
Concrètement faut faire quoi ? Ainsi dans mon premier exemple ci-dessus,
dois-je écrire

t=(enum toto) 28;

?

Merci.

10 réponses

3 4 5 6 7
Avatar
candide
Wykaaa a écrit :
enum {VRAI, FAUX};



Ceci est à bannir de toute façon !



Pourquoi ?

Tu voulais dire la grammaire sous forme de BNF (Backus-Naur Form) ?




Heu, oui.


Ceci dit, j'estime que tout programmeur (quel que soit le langage dans
lequel il code) doit comprendre la notation BNF d'un langage de
programmation. Ça fait partie des connaissances de base de son métier.



Il suffit qu'on lui ait enseigné. C'est pas difficile une fois qu'on
vous l'a expliqué.



Ce n'est pas la concision qui fait du beau code.

Du beau code c'est du code facilement maintenable, testable,
réutilisable, facile à faire évoluer, etc.
Du beau code, c'est du code qui répond aux facteurs, caractéristiques et
sous-caractéristiques qualité tels que décrits dans la norme ISO 9126 et
qu'on sait mesurer (métrologie du logiciel).




Une norme qui mesure la beauté, quelle horreur !!!

En appliquant au maximum ce qui est dit dans ce livre et en cherchant à
bien s'imprégner des exemples, on ne peut qu'écrire du meilleur code C




Alors, justement, regardons ce qui est dit sur les enum :

Il dit que

enum couleurs {bleu, blanc, rouge} a, b;

équivaut à déclarer

const int bleu=0, blanc =1, rouge =2;
int a,b;


Il ne recommande pas d'initialiser soi-même les constantes d'une
énumération.


Concernant les casts, il dit :

règle : "expliciter les conversions inévitables par des forçages de type"

et il donne ceci en exemple (page 215):

--------------------------------------

enum gamme_do {do, re, /* etc */} n1, n2;
int i;

i=(int)n1;
n1=(enum gamme_do)((int)n2-1);

--------------------------------------

Des réactions ?
Avatar
Wykaaa
Marc Espie a écrit :
In article <487145fa$0$900$,
Wykaaa wrote:
- Les enum doivent être utilisés conjointement avec switch.



Marrant, j'ai tendance a bannir switch de mon code.
- il n'y a rien que fasse switch qui ne se fasse pas a coup de
if
else if
else if
else
- si on oublie un break, on est dans la merde
- les conversions de type sont non triviale
- si on met deux fois la meme valeur, ca ne compile plus.

Le dernier cas est particulierement subtil et vicieux, combien de fois
suis-je tombe sur du code qui faisait des switch(errno)... en oubliant
allegrement que certaines valeurs sont identiques sur certains OS.



Je suis d'accord que switch n'ajoute rien à la programmation structurée
puisque les cascades de if...else font la même chose. Le switch n'est
qu'un confort syntaxique qui permet tout de même d'améliorer la
lisibilité du code.
1) Oubli du "break"
On ne doit JAMAIS oublier le "break" (même sur "default" quand celui-ci
est à la fin du switch). Si un "case" n'a pas de break, on doit
documenter pourquoi, sauf dans les cas où on a écrit (factorisation de
case) :
case MACHIN :
case BIDULE :
faire_ceci();

2) Conversions
Je ne comprends pas ta remarque. S'il y a des conversions implicites de
type c'est que le code est mal écrit.

3) Même valeur
Et bien justement, ça permet de voir qu'il y a une erreur puisque ça ne
compile plus !
Avatar
candide
Marc Boyer a écrit :

Tu fais référence à quoi ? "the values can be generated for you" ?



oui


(2) phrase très ambiguë. Je vais quand même avoir un problème si je mets
un flottant dans une variable de type enum toto, ça le compilateur il va
le voir.



Je pense que ce qu'il veut signifier est que tu peux écrire:
enum T {a, b, c};
enum T e= 3;



Oui, je pense aussi que c'est ça.


(3) le checking, un réel avantage ? meilleure sécurité ?



Ben, oui, ça me paraît une évidence, non ?



Que ça TE paraisse une évidence, j'en suis certain. En ce qui me
concerne, non, je suis très peu sensibilisé à ça et depuis très peu de
temps en plus. On a d'autres chats à fouetter quand on apprend le C. Le
type est vérifié à la compilation ? OK très bien, question suivante !
Je connaitrais d'autres langages avec un checking différent, genre
python (à mon programme pour les mois à venir, oui, je sais qu'il y en a
ici qui trouve que c'est un langage jouet mais j'ai plus envie de me
prendre la tête), peut-être comprendrais-je mieux.
Avatar
candide
Wykaaa a écrit :
(1) the values can be generated for you ?



Ça c'est autre chose.
Quand tu écris enum Jour {LUNDI, MARDI, MERCREDI, JEUDI, VENDREDI,
SAMEDI, DIMANCHE}, c'est le compilateur qui fait l'association :
LUNDI -> 0, MARDI -> 1, etc. alors qu'avec #define, c'est toi qui donne
la correspondance.



OK, merci je pense en effet que c'est ça qu'ils voulaient dire.
Avatar
espie
In article <487207cd$0$893$,
Wykaaa wrote:
1) Oubli du "break"
On ne doit JAMAIS oublier le "break" (même sur "default" quand celui-ci
est à la fin du switch). Si un "case" n'a pas de break, on doit
documenter pourquoi, sauf dans les cas où on a écrit (factorisation de
case) :
case MACHIN :
case BIDULE :
faire_ceci();



Ouais, mais tu te reposes sur des warnings du compilo, et pas des erreurs.

2) Conversions
Je ne comprends pas ta remarque. S'il y a des conversions implicites de
type c'est que le code est mal écrit.



Oui, d'ailleurs la plus simple des facons d'eviter ca, c'est de ne pas
utiliser de switch...

La-aussi, je suis tombe tres (trop) souvent sur du code mal ecrit.

En pratique, on a tres souvent des conversions implicites, entre int,
unsigned char, et char, par exemple.

Le probleme de switch, c'est que c'est asymetrique... le paragraphe de
la norme qui couvre ca (6.8.4.2.5) est sournois:
|The integer promotions are performed on the controlling expression.
|The constant expression in each *case* label is converted to the promoted type
|of the controlling expression. If a converted value matches that of the
|promoted controlling expression, control jumps to the statement following
|the matched *case* label. [...]

Par opposition a if (e == e2), qui au moins est symetrique.


3) Même valeur
Et bien justement, ça permet de voir qu'il y a une erreur puisque ça ne
compile plus !



Mais trop tard... la notion de code portable, tu connais ? Du code a base
de switch, c'est une vraie bombe a retardement dans pas mal de contextes...
En particulier, ca peut tres bien marcher sur 90% des machines, et ne pas
compiler du tout sur les 10% restantes....

Je pense au
switch(errno) {
case EFOO:
first code
break;
case EBAR:
case EBARTOO
second code;
break;
}

qui va compiler sur toutes les machines ou EBAR != EBARTOO, et pas sur les
autres...

C'est malheureusement un gros classique.


M'enfin bon, c'est le gros defaut du C... il y a des chausse-trappes un
peu partout. Ca marche nickel dans 99% des cas, et ca merdoie le reste
du temps. Le probleme, quand par exemple, tu essaies d'expliquer C a
des debutants, c'est que c'est tres difficile d'expliquer le 1% restant
sans donner enormement de contexte... tu peux donc donner des trucs qui
fonctionnent 99% du temps (en sachant fort bien que parfois, ca va merder)
ou essayer de te limiter aux trucs qui fonctionnent 100% du temps.

Pour ma part, c'est l'effet boomerang: comme je passe pas mal de temps
a faire marcher du code ecrit par des debutants ou des mauvais, meme
si c'est pas les miens, j'essaie d'en lacher le moins possible dans la
nature. Meme si leur code n'utilise pas 100% du langage, au moins il sera
fiable (enfin, s'ils suivent mes conseils, ce qui n'est pas gagne...).
Avatar
Wykaaa
candide a écrit :
Wykaaa a écrit :
enum {VRAI, FAUX};



Ceci est à bannir de toute façon !



Pourquoi ?

Tu voulais dire la grammaire sous forme de BNF (Backus-Naur Form) ?




Heu, oui.


Ceci dit, j'estime que tout programmeur (quel que soit le langage dans
lequel il code) doit comprendre la notation BNF d'un langage de
programmation. Ça fait partie des connaissances de base de son métier.



Il suffit qu'on lui ait enseigné. C'est pas difficile une fois qu'on
vous l'a expliqué.



Là, ce sont les cursus informatiques qui sont à revoir. L'informatique
est enseignée n'importe comment dans les écoles (toutes les écoles...).



Ce n'est pas la concision qui fait du beau code.

Du beau code c'est du code facilement maintenable, testable,
réutilisable, facile à faire évoluer, etc.
Du beau code, c'est du code qui répond aux facteurs, caractéristiques
et sous-caractéristiques qualité tels que décrits dans la norme ISO
9126 et qu'on sait mesurer (métrologie du logiciel).




Une norme qui mesure la beauté, quelle horreur !!!


Ce n'est pas ce que j'ai dit. Tu extrapoles. On doit savoir mesurer les
qualités d'un logiciel sinon on peut toujours énoncer des principes, ce
ne sont, au mieux, que des voeux pieux.

En appliquant au maximum ce qui est dit dans ce livre et en cherchant
à bien s'imprégner des exemples, on ne peut qu'écrire du meilleur code C




Alors, justement, regardons ce qui est dit sur les enum :

Il dit que

enum couleurs {bleu, blanc, rouge} a, b;

équivaut à déclarer

const int bleu=0, blanc =1, rouge =2;
int a,b;


Il ne recommande pas d'initialiser soi-même les constantes d'une
énumération.


Concernant les casts, il dit :

règle : "expliciter les conversions inévitables par des forçages de type"

et il donne ceci en exemple (page 215):

--------------------------------------

enum gamme_do {do, re, /* etc */} n1, n2;
int i;

i=(int)n1;
n1=(enum gamme_do)((int)n2-1);

--------------------------------------

Des réactions ?



Ben oui. c'est exactement ce que j'ai dit par ailleurs : toujours
expliciter les conversions.
Décidément il est vraiment bien Philippe Drix !
Avatar
Pierre Maurette
Marc Espie, le 07/07/2008 a écrit :
In article <487145fa$0$900$,
Wykaaa wrote:
- Les enum doivent être utilisés conjointement avec switch.



Marrant, j'ai tendance a bannir switch de mon code.
- il n'y a rien que fasse switch qui ne se fasse pas a coup de
if
else if
else if
else
- si on oublie un break, on est dans la merde
- les conversions de type sont non triviale
- si on met deux fois la meme valeur, ca ne compile plus.



J'ai certainement en d'autres temps écrit la même chose. Il faut dire
que j'étais alors déçu en découvrant le switch du C après avoir
pratiqué celui de Pascal (Delphi). Puis j'ai mis de l'eau dans mon vin
(pouah).

J'ai d'abord constaté que c'était quand même assez pratique, lisible,
main,tenable, en utilisation tout à fait standard, sans originalité, en
association systématique avec justement une enum. Et les outils de 2008
- et depuis longtemps - permettent de maîtriser une syntaxe que je
trouvais effectivement un peu piquante.

Et puis je me suis un jour rendu compte que le switch...case était un
"goto label" à peine déguisé. Et, de fil en aiguille, que si
généralement on peut remplacer le switch...case par un if...else
if...else if...else, les deux algorithmes n'étaient tout simplement pas
du tout les mêmes. En rapport avec "deux fois la même valeur".

Le switch...case est un branchement selon valeur. Il peut être sans
test et à nombre de cycles constant quelque soit l'aiguillage. Ceci
d'autant plus facilement qu'on utilise l'enum en bon père de famille,
c'est à dire en laissant le compilateur affecter les valeurs de 0 à N,
et pourquoi pas en type compatible avec une adresse ou un offset. Je
vous fais grâce de la structure du code machine que j'ai en tête, c'est
évident. Je dois avouer que sur un seul essai trop rapide et avec juste
une paire de cas, je n'ai pas mis ce truc en évidence. Mais il faudrait
refaire soigneusement des essais, avec plusieurs compilateurs et
surtout un aiguillage sur quelques centaines de messages.

A l'opposé, dans un aiguillage par un if...else if...else if...else,
vous imposez un ordre de traitement. Ça permet de hiérarchiser, et donc
de traiter "deux fois la même valeur". Ça peut être intéressant si on a
un cas qui se présente statistiquement "presque toujours". Si bien sûr
on en tient compte, ce qui est quand même la moindre des choses.


Le dernier cas est particulierement subtil et vicieux, combien de fois
suis-je tombe sur du code qui faisait des switch(errno)... en oubliant
allegrement que certaines valeurs sont identiques sur certains OS.



Effectivement, ce contexte n'est pas adapté au switch...case, qui est
parfaitement défini. D'autant que ceci:

switch(x){
case 1:
case 2:
case 1:
/* etc. */

ne compile pas, alors que "ça pourrait".

--
Pierre Maurette
Avatar
Wykaaa
Pierre Maurette a écrit :
Marc Espie, le 07/07/2008 a écrit :
In article <487145fa$0$900$,
Wykaaa wrote:
- Les enum doivent être utilisés conjointement avec switch.



Marrant, j'ai tendance a bannir switch de mon code.
- il n'y a rien que fasse switch qui ne se fasse pas a coup de
if
else if
else if
else
- si on oublie un break, on est dans la merde
- les conversions de type sont non triviale
- si on met deux fois la meme valeur, ca ne compile plus.



J'ai certainement en d'autres temps écrit la même chose. Il faut dire
que j'étais alors déçu en découvrant le switch du C après avoir pratiqué
celui de Pascal (Delphi). Puis j'ai mis de l'eau dans mon vin (pouah).

J'ai d'abord constaté que c'était quand même assez pratique, lisible,
main,tenable, en utilisation tout à fait standard, sans originalité, en
association systématique avec justement une enum. Et les outils de 2008
- et depuis longtemps - permettent de maîtriser une syntaxe que je
trouvais effectivement un peu piquante.

Et puis je me suis un jour rendu compte que le switch...case était un
"goto label" à peine déguisé. Et, de fil en aiguille, que si
généralement on peut remplacer le switch...case par un if...else
if...else if...else, les deux algorithmes n'étaient tout simplement pas
du tout les mêmes. En rapport avec "deux fois la même valeur".



Dans le compilateur C que nous avions fait, le switch générait un
tableau de branchement ce qui évitait de générer des tests pour se
brancher. Sur la machine cible, on disposait d'une instruction
assembleur de branchement avec adressage indexé.
Oooops, je m'aperçois que c'est ce que tu dis juste ci-dessous.
Il est clair que, du coup, les 2 algos ne sont pas les mêmes...

Le switch...case est un branchement selon valeur. Il peut être sans test
et à nombre de cycles constant quelque soit l'aiguillage. Ceci d'autant
plus facilement qu'on utilise l'enum en bon père de famille, c'est à
dire en laissant le compilateur affecter les valeurs de 0 à N, et
pourquoi pas en type compatible avec une adresse ou un offset. Je vous
fais grâce de la structure du code machine que j'ai en tête, c'est
évident. Je dois avouer que sur un seul essai trop rapide et avec juste
une paire de cas, je n'ai pas mis ce truc en évidence. Mais il faudrait
refaire soigneusement des essais, avec plusieurs compilateurs et surtout
un aiguillage sur quelques centaines de messages.


Lorsqu'il y a peu de cas, ce n'est pas forcément intéressant. Par contre
avec de nombreux cas, le tableau de branchement est forcément plus
performant (à condition de disposer, sur la machine cible, des
instructions assembleur "qui vont bien") et permet de traiter les
"tableaux creux".

A l'opposé, dans un aiguillage par un if...else if...else if...else,
vous imposez un ordre de traitement. Ça permet de hiérarchiser, et donc
de traiter "deux fois la même valeur". Ça peut être intéressant si on a
un cas qui se présente statistiquement "presque toujours". Si bien sûr
on en tient compte, ce qui est quand même la moindre des choses.



Mais dans ce cas, le programmeur ne devrait pas choisir le switch.
Encore faut-il qu'il connaisse la répartition des probabilités des cas...
Or, cette répartition est généralement connue très en amont car elle
résulte des "processus métier".


Le dernier cas est particulierement subtil et vicieux, combien de fois
suis-je tombe sur du code qui faisait des switch(errno)... en oubliant
allegrement que certaines valeurs sont identiques sur certains OS.



Effectivement, ce contexte n'est pas adapté au switch...case, qui est
parfaitement défini. D'autant que ceci:

switch(x){
case 1:
case 2:
case 1:
/* etc. */

ne compile pas, alors que "ça pourrait".



Le C dans toute sa splendeur :-)
Avatar
Mickaël Wolff
Marc Boyer a écrit :

Tient. Et tu crois que c'est programmé en quoi un avion ?



En ADA, comme pour Ariane V :-D je suis déjà parti ! -->[]


--
Mickaël Wolff aka Lupus Michaelis
http://lupusmic.org
Avatar
Pierre Maurette
Wykaaa, le 07/07/2008 a écrit :
Marc Espie a écrit :



[...]

Des fonctions locales sont, en générale, des fonctions qui servent à
"implémenter" la fonction dans laquelle elles sont définies mais on ne veut
pas qu'un "utilisateur" les voit (encapsulation). En C++, on pourrait les
déclarer dans a partie privée d'une classe.



J'appelerais plutôt ça des fonctions internes. J'utilise des fonctions
internes dans d'autres langages, en C ça ne me manque pas. J'utilise ce
que j'appelle des fonctions locales. Dans le .c du module, je définis
une fonction static, en fait généralement static inline. Je décore le
nom, un underscore final, c'est peut-être pas bien, je ne sais pas. Je
déclare en général ces fonctions en tête, pour ne pas être emmerdé avec
des références forward. Bien entendu, rien dans le .h.
Dans ces conditions, la fonction locale ne voit pas les variables non
masquées de la fonction appelante, mais comme en général elle est
inlinée, on s'en fiche un peu. Et puis une même fonction locale peut
être utilisée dans plusieurs fonctions, ce qui va dans le sens de la
centralisation - non duplication du code.
Ça m'arrive même de déclarer une static inline f_() et une f() déclarée
et documentée dans le .h qui appelle juste f_().
Tout ça c'est des trucs perso, je pense et j'espère que je réinvente la
roue, mais je crains et j'admettrais que je fais quelques conneries ;-)

--
Pierre Maurette
3 4 5 6 7