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

1 2 3 4 5
Avatar
Marc Boyer
On 2008-07-06, Wykaaa wrote:
C'est indéniable que c'est un avantage et que c'est un (petit) pas en
avant vers du code plus sécurisé. Cependant, écrire du code fiable de
bout en bout en C me paraît seulement à la portée des 5% de programmeurs
C qui maîtrisent totalement le langage (et encore).
En clair, si l'objectif de l'application est d'être extrêmement fiable
(car risque de perte de vies humaines sinon), je ne l'écrirait jamais en
langage C !!!



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

Marc Boyer
--
Si tu peux supporter d'entendre tes paroles
Travesties par des gueux pour exciter des sots
IF -- Rudyard Kipling (Trad. André Maurois)
Avatar
espie
In article <48714c38$0$904$,
Wykaaa wrote:
- la structure de bloc qui est bancale (on ne peut pas définir de
fonction à l'intérieur d'une fonction). Il n'y a donc pas de notion de
fonction locale (il n'y a que la notion de fonction statique...)



Jamais compris a quoi ce genre de choses pouvait servir.
A la limite, j'aurais plus besoin de definir des pointeurs sur des
fonctions anonymes, ou de regrouper mes fonctions en classe, mais c'est
super-rare que j'ai besoin de fonctions locales pour organiser du code.

- le typedef qui introduit une liaison forte entre les niveaux lexical
et syntaxique et qui n'est pas du "vrai" typage mais seulement de
l'homonymie.



Il n'y a pas de typage en C. C'est tellement leger que c'en est inexistant.

Un type enum est un ... type. #define n'indique aucun type pour un nom
et ce n'est pas une variable... (aucun contrôle ne peut être effectué
par le compilateur).



Allez, quelles sont les erreurs qu'enum permet de rattraper, cote typage ?
perso, je n'en vois aucune. Si c'est une enum, ca marche comme un entier,
et le compilo va peut-etre daigner donner un warning si on affecte un 1
a une enum... ca ne va pas tres loin.
Avatar
Wykaaa
candide a écrit :
Wykaaa a écrit :
Il est vrai que la littérature sur le langage C n'est pas terrible, à
commencer par le livre des auteurs du langage lui-même dans lequel de
nombreux exemples de code sont justement plutôt des exemples de ce
qu'il ne faudrait pas écrire en C...



Pas tant que ça je trouve, tu pensais à quoi ?


Il faut utiliser les enum en C pour ce qu'ils sont et les services
qu'ils peuvent rendre :
- augmenter la lisibilité des programmes. Il est toujours préférable
de, par exemple, nommer les jours que de se servir de 1, 2, 3, ..., 7
- Les enum doivent être utilisés conjointement avec switch.




C'est aussi comme ça que je vois les choses, il suffit de lire comment
sont gérées les touches du clavier d'un quelconque jeu écrit en C : des
enum et des switch. Et c'est très lisible.



Voilà. Les enum permettent d'augmenter la lisibilité donc la
maintenabilité et l'évolutivité des programmes


D'une façon générale, il faut utiliser les enums en C comme ils sont
utilisés dans les autres langages, en particulier ADA.
Et surtout, il ne faut JAMAIS mélanger les enum et les int !



Donc tu abondes dans le sens de HS. Est-ce que tu aurais un exemple
précis du problème que ça peut poser ?

Donc un truc comme ça est fautif selon toi :


enum booleen {FAUX, VRAI} v1, v2=1-v1;


?



Tout à fait fautif en effet, d'autant plus que v1 n'est pas initialisé
explicitement... et que les variables v1 et v2 sont déclarées dans la
foulée du type...
C'est exactement le genre d'exemple de code que je sanctionne dans les
audits et chez mes étudiants.

Ta ligne ci-dessus est un condensé de tout ce qu'il ne faut pas écrire
en C...
Avatar
espie
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.
Avatar
Wykaaa
candide a écrit :
Wykaaa a écrit :
langage. Forcement moins exact que la norme, par construction, mais
egalement
sensiblement plus digeste.



Même pas sûr.



Oh que si !



Oh que non ! Un exemple précis : Harbison and Steele sont infoutus dans
leur bouquin de définir ce que recouvre précisément le terme de
"statement" et il est FONDAMENTAL d'avoir des définitions précises en la
matière (cf. d'ailleurs la confusion déjà citée entre statement et
declaration). Dans HS, AUCUNE définition (alors qu'il y a un chapitre
portant le nom de "Statements" (chapitre 8). Incroyable ! de
l'amateurisme ! Et un jour, j'ai ouvert la norme et j'ai vu que le corps
du document contient des définitions relativement précises (en
particulier de "statement"). Et au début du texte il y a une clause qui
s'appelle "Definitions and conventions". Et tout a été amélioré entre
les versions de C90 à C99. Quand j'ai commencé le C, j'ai été accablé
par le jargon et un ouvrage de référence se doit d'en donner des
définitions, c'est évident.

Autre exemple : dans le § 5.5 sur les enumérations justement, ils ne
donnent AUCUN exemple d'une contruction d'une déclaration anonyme
d'énumération, un truc du genre :

enum {VRAI, FAUX};



Ceci est à bannir de toute façon !


eux systématiquement, ils écrivent

enum nonyme {VRAI, FAUX};



Ils ont raison. C'est la bonne pratique.

La norme, elle, donne des exemples pensés, et, ELLE, donne un tel
exemple. Or si je n'ai aucun exemple de certaines constructions (en plus
ici relativement usuelles), je ne vois pas comment je vais penser à les
utiliser à moins que je ne décide de déchiffrer la grammaire BNS du
langage C (ce qui est la présentation faite par HS et qu'ils
n'expliquent jamais, comme si tous les programmeurs de C connaissaient
la forme BNS d'une grammaire).



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

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.

Et je pourrais multiplier les exemples où la norme est plus claire et
plus satisfaisante que HS.


C'est une preuve d'honnêteté de la part des auteurs. D'autres, moins
scrupuleux, laissent leurs erreurs traîner de version en version...



En effet mais je m'étonne qu'à la 5ème édition, il en reste autant.



Le K&R est anti-pédagogique !
Il m'est arrivé plusieurs fois de préconiser aux participants d'un
cours sur le langage C de mettre ce livre à la poubelle. C'est un des
plus mauvais sur le langage (pour l'apprentissage, je préfère le
Harbison et Steele, c'est peu dire...)



Je n'aime pas trop le K&R car il m'a fait beaucoup souffrir. Mais plus
je connais le C plus je trouve que le livre a des qualités de synthèse.
Et du beau code bien concis.



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).


J'aimais bien le bouquin de Philippe Drix : langage C, norme ANSI,
sous-titré, vers une approche objet.




J'ai beaucoup aimé ce livre même s'il n'est pas si facile à lire. Et il
m'a aidé à fixer pas mal de choses.




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
(bien meilleur qu'en s'inspirant du K&R).
Avatar
Wykaaa
candide a écrit :
Wykaaa a écrit :


Ta phrase tendrait à dire, par analogie, que tu es plus intéressé par
l'apprentissage du dictionnaire et de la grammaire française que par
l'écriture du français ??
Ça paraît bizarre...



Non, il y en a qui aiment écrire des romans, il y en a qui aiment les
lire, il y en a qui aiment les lire et les relire, il y en a qui aiment
les étudier. Ou si on veut être mauvaise langue, il y a les créateurs et
il y a les critiques.

Pour moi :
- les macros-fonctions
- les chaînes de caractères avec cette stupidité du pour marquer la
fin de chaîne. Il aurait plutôt fallu faire comme en PL/1
- l'équivalence pointeur/tableau
- la structure de bloc qui est bancale (on ne peut pas définir de
fonction à l'intérieur d'une fonction). Il n'y a donc pas de notion de
fonction locale (il n'y a que la notion de fonction statique...)
- le trop grand nombre de conversions implicites
- le typedef qui introduit une liaison forte entre les niveaux lexical
et syntaxique et qui n'est pas du "vrai" typage mais seulement de
l'homonymie.



OK mais dans tout ça, il y a plein de choses incontournables,
l'équivalence tableau-pointeur on peut pas s'en passer (Marc parlait des
choses à ne pas utiliser) et avec lesquelles on apprend très bien à
vivre (et qui me manqueront peut-être quand j'apprendrai autre chose que
C).



Bien sûr que si qu'on peut se passer de l'équivalence pointeur/tableau
en C. Il suffit de programmer proprement.
C'est K&R qui font croire qu'on ne peut s'en passer...


(1) je comprends ce qu'il veut dire par ça. En clair, c'est quoi
l'avantage ? qu'on dispose de vraies variables ?



Un type enum est un ... type. #define n'indique aucun type pour un nom
et ce n'est pas une variable... (aucun contrôle ne peut être effectué
par le compilateur).



Je veux bien mais en quoi cela est-il la "traduction" de

(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.




(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.



Ouais. Cette phrase est bizarre mais il y en a plein des comme ça dans
leur bouquin et ça reflète bien leur esprit assez tordu, il faut le
dire...




Esprit tordu ? Non, je ne pense pas, plutôt esprit malin, tellement
malin que leur livre est incompréhensible (sauf par certains esprits
avancés dont il existe ici quelques spécimens) par le débutant. Ils sont
tellement le langage dans la peau qu'il ne réalisent pas la polysémie de
certaines de leurs phrases.


Esprit malin ?
Pour moi, il n'y a rien de malin à écrire du code super concis que
personne n'arrivera à comprendre sauf l'auteur.
Ça serait plutôt le signe d'un ego surdimensionné.
Je commence toujours un cours de programmation en écrivant en gros au
tableau : EGOLESS PROGRAMMING
On ne met pas son ego dans la programmation car un programme (en milieu
professionnel) est un produit INDUSTRIEL et, en tant que tel, doit
respecter les qualités d'un produit industriel.

avant vers du code plus sécurisé. Cependant, écrire du code fiable de
bout en bout en C me paraît seulement à la portée des 5% de
programmeurs C qui maîtrisent totalement le langage (et encore).



Et si c'était tout simplement parce que la doc est mal faite ?



Non, c'est le langage qui est mal foutu car il permet aux programmeurs
d'écrire à peu près n'importe quoi (ceci dit, la majorité des
programmeurs considèrent cela comme une qualité car les programmeurs
n'aiment pas les contraintes, en général).
Avatar
Wykaaa
candide a écrit :
Wykaaa a écrit :

Tiens "enum statement" ? ça existe ça ? Ce serait pas plutôt "enum
declaration" ?


Cette confusion est habituelle chez K&R.



Ah bon ? confusion entre "statement" et "declaration" ou juste entre
"enum statement" etc "enum declaration" ? t'as un exemple précis ?



Oui des confusions entre "statement" et "declaration" et pas seulement
pour les enum.

A l'origine, K&R voulaient que tout ce qu'on écrivait en C ait un sens.
On pouvait d'ailleurs écrire i[T] aulieu de T[i] et ceci était
interprété correctement(au début C était un langage interprété).

Ils ne faisaient pas la distinction entre "statement" et "declaration" :
tout était potentiellement une "instruction".
C'est cet historique qui fait que K&R ne sont pas toujours très
rigoureux dans le vocabulaire.
Avatar
Wykaaa
Pierre Maurette a écrit :
candide, le 03/07/2008 a écrit :
Bonjour,



Bonjour

[...]

Si, par exemple

enum toto {A4, BQ} t;

peut-on écrire

t(;



Oui et non.
Oui parce que ça compilera. Le seul risque est de passer pour un idiot à
la relecture, ou de faire sombrer le relecteur dans des abîmes de
perplexité s'il cherche à comprendre quelque chose.
Non parce que une obfuscation sans intérêt est simplement une erreur. De
plus ce code est certainement en désaccord avec toutes les règles de
style, et que violer une règle de style est une erreur.
Notez que la valeur n'a rien à voir. tQ serait peut-être encore plus
mauvais.
Bien entendu, le compilateur est à mon sens autorisé à warner, et un
lint quelconque à vous châtier.


(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;

?



Les valeurs négatives sont valides dans les enum. Et au passage rien
n'impose, sauf à respecter des règles de style, l'absence de doublons
implicites ou explicites:

enum toto {A = 34, B = 33, B1, C = 34, D = -1000} t;



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 ?



Oublier un peu la norme et la littérature et se fixer des règles de bons
sens. Les enum ne sont jamais indispensables, et directement
intéressantes essentiellement en liaison avec un switch...case (et à mon
avis c'est tout à fait réciproque). Et dans quelques cas déterminés par
le fait que la valeur sous-jacente des termes de l'enum ne doit pas
importer du tout, comme pour le switch...case. On s'imposera alors les
règles:
- Laisser l'enum se démerder, donc ne pas utiliser le forçage de valeurs.
- Ne jamais présumer ni même tenir compte de la valeur numérique d'une
variable du type de l'enum. A une exception, si on ne fait pas de
forçage, on peut utiliser l'ordre dans lequel on a initialisé l'enum et
donc faire des comparaisons entre variables du même type enum.
- Ne jamais utiliser autre chose que des termes de l'enum, donc jamais
de valeurs numériques, dans le switch...case, ou un tableau, si on
décide de l'indicer par l'enum. Pas de résultats d'opérations entre des
variables de type de l'enum, ces résultats n'étant d'ailleurs pas dans
l'enum à priori.

Un exemple, en sachant qu'une structure serait sans doute plus adaptée:

enum{
COTE_FENETRE,
NB_FENTRES,
HAUTEUR_FENETRE,
LARGEUR_FENETRE,

UNUSED_SIZE_ELEM
};

unsigned long GEOMETRIE[UNUSED_SIZE_ELEM];

Je ne mets pas de cast sur UNUSED_SIZE_ELEM, je n'en vois pas l'utilité.
On va pouvoir utiliser des trucs comme GEOMETRIE[HAUTEUR_FENETRE],
supposés être lisibles. Si on n'utilise jamais autre chose qu'un membre
de l'enum pour indicer GEOMETRIE, on peut en cours du développement sans
souci réordonner le machin, insérer de nouveau membres où bon nous
semble, et supprimer des membres de l'enum. Dans ce dernier cas, si on
continue à l'utiliser à un endroit, on aura une erreur.


Il y a un cas qui fait exception à toutes ces règles, et qui est
peut-être envisagé dans la norme. Ce sont les enum composées de valeurs
entières avec un seul bit allumé. On pourra s'autoriser une mise en OU,
pour ensuite faire une lecture par masquage par les éléments de l'enum.
Il faudra quand même être prudent, la taille exigée monte très vite.


Ainsi dans mon premier exemple ci-dessus, dois-je écrire

t=(enum toto) 28;

?



??

A mon avis, ce qui est suggéré ici est que: imaginons qu'on soit
pragmatique et dans une République idéale sans la double Corse 2A 2B.
Nous pourrions penser nous simplifier la vie avec une enum des noms de
départements en correspondance avec leurs numéros. Nous obtiendrions
celà en copiant-collant une liste, puis en forçain AIN à 1, et
GUADELOUPE à 971. Je ne le ferais pas, sauf pour un programme jetable,
mais peu importe.
Maintenant nous voulons obtenir la chaîne "01" à partir de AIN, valeur
1. Alors, le texte suggère d'utiliser (int)AIN dans le ?printf().
Ou pour tester si un département est un DOM (comme la Corse ?), faire:
isDom(int dep){
return dep >= (int)GUADELOUPE && dep <= (int)LA_REUNION;
}

Tout celà fonctionnerait sans le cast, mais c'est certainement correct
de le systématiser.




Je suis tout à fait d'accord avec tout ce que tu dis dans ce message.
Avatar
candide
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.



La liste s'allonge !
Avatar
espie
In article <4871ef49$0$934$,
Wykaaa wrote:
Bien sûr que si qu'on peut se passer de l'équivalence pointeur/tableau
en C. Il suffit de programmer proprement.
C'est K&R qui font croire qu'on ne peut s'en passer...



Ca depend de ce que tu entends par `s'en passer'.

Ton code peut donner l'impression de ne pas s'en servir mais, de toutes
facons, des que tu ecris t[i], tu *utilises* cette equivalence, puisque
de toutes facons, par construction, c'est la facon dont cette construction
est evaluee: elle est indiscernable de *(t+i).

C'est cable dans le langage. La seule facon de s'en passer, c'est de ne
pas du tout utiliser de tableaux.

Sinon, tu fais des equivalences tableau/pointeur, le langage ne te laisse
pas le choix. Par exemple, void f(int *) et void f(int []) sont une seule
et unique signature de fonction. C'est meme pas une equivalence, le
compilateur ne fait pas la difference.

Tu peux faire toutes les differences que tu voudras dans ta tete, tu auras
exactement zero aide du systeme cote typage pour s'assurer que tout va
bien.

Pour le `il suffit de programmer proprement', c'est tout le probleme du C.
Tu as un langage tres faiblement type (autant dire pas du tout, j'exagere
a peine). Sur du code ecrit soi-meme, en faisant attention, on peut faire
des trucs qui fonctionnent. Sur du gros code, c'est tres dur d'etre sur
que tout est bon (et en plus, il y a plein de gorets qui ecrivent du code).

Il y a quelques annees, j'avais experimente avec splint, un super-lint qui
se repose sur des annotations pour `rajouter' du typage et des controles
en C. Meme pour ecrire un programme relativement simple, c'etait complexe
d'avoir quelque chose de parfait (entre autres parce que des pans entiers
de la bibliotheque manquaient d'annotation). Ce systeme avait l'air fort
sympathique, en particulier parce qu'il permettait d'avoir un vrai typage
plus fort (en particulier, de virer les equivalences entre typedef et
de considerer les typedef comme des types abstraits).

A l'arrivee: beaucoup d'efforts, un resultat moyen, et la conviction
profonde que, de toutes facons, il n'y a rien a sauver. C'est impossible
de transformer le C en langage fortement type sans changer profondement
le langage. J'encourage ceux d'entre vous qui n'y croient pas a essayer.
Splint est disponible dans toutes les bonnes cremeries, et ca prend
juste quelques heures a prendre en main.

C'est un des trucs qui m'a fait considerer sous un jour nouveau le
boulot de Stroustrup: il a quand meme reussi le tour de force de rajouter
une couche objet *propre*, puis de la programmation generique, en gardant
99% de la semantique d'un langage qui est quand meme cradouille au
possible...
1 2 3 4 5