--------------------------- 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
- Les enum doivent être utilisés conjointement avec switch.
Marrant, j'ai tendance a bannir switch de mon code.
La liste s'allonge !
Que veux-tu, avec l'experience, plus on ecrit du code, plus on tombe sur les memes erreurs. C'est bien plus simple de rester sur un sous-ensemble bien defini et sans erreur.
Typiquement, c'est bien agreable de pouvoir faire du C99... Rien de plus utile que d'avoir des fonctions inline (ce qui enleve les 9/10 des utilisations deplaisantes de macro) ou des types entiers bien definis (au revoir la jungle des INT32 et assimiles des annees 80).
La seule vraie difficulte, c'est d'eviter de definir son propre sous-langage, avec ses idiosyncrasies qui font qu'on est tout seul a parler le langage en question (comme DJB, par exemple).
Mais bon, comme je ne bosse pas tout seul, ca n'est pas un vrai probleme...
In article <4871f2a3$0$24368$426a74cc@news.free.fr>,
candide <candide@free.invalid> wrote:
Marc Espie a écrit :
In article <487145fa$0$900$ba4acef3@news.orange.fr>,
Wykaaa <wykaaa@yahoo.fr> wrote:
- Les enum doivent être utilisés conjointement avec switch.
Marrant, j'ai tendance a bannir switch de mon code.
La liste s'allonge !
Que veux-tu, avec l'experience, plus on ecrit du code, plus on tombe sur
les memes erreurs. C'est bien plus simple de rester sur un sous-ensemble
bien defini et sans erreur.
Typiquement, c'est bien agreable de pouvoir faire du C99... Rien de plus
utile que d'avoir des fonctions inline (ce qui enleve les 9/10 des utilisations
deplaisantes de macro) ou des types entiers bien definis (au revoir la jungle
des INT32 et assimiles des annees 80).
La seule vraie difficulte, c'est d'eviter de definir son propre sous-langage,
avec ses idiosyncrasies qui font qu'on est tout seul a parler le langage
en question (comme DJB, par exemple).
Mais bon, comme je ne bosse pas tout seul, ca n'est pas un vrai probleme...
- Les enum doivent être utilisés conjointement avec switch.
Marrant, j'ai tendance a bannir switch de mon code.
La liste s'allonge !
Que veux-tu, avec l'experience, plus on ecrit du code, plus on tombe sur les memes erreurs. C'est bien plus simple de rester sur un sous-ensemble bien defini et sans erreur.
Typiquement, c'est bien agreable de pouvoir faire du C99... Rien de plus utile que d'avoir des fonctions inline (ce qui enleve les 9/10 des utilisations deplaisantes de macro) ou des types entiers bien definis (au revoir la jungle des INT32 et assimiles des annees 80).
La seule vraie difficulte, c'est d'eviter de definir son propre sous-langage, avec ses idiosyncrasies qui font qu'on est tout seul a parler le langage en question (comme DJB, par exemple).
Mais bon, comme je ne bosse pas tout seul, ca n'est pas un vrai probleme...
Erwan David
Pierre Maurette écrivait :
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.
J'ai un contre exemple...
Une spécification de machine virtuelle donne les noms des opcodes et leur valeur numérique. L'utilisation d'un enum en forçant les valeurs a des avantages en terme de lisibilité, et de debug (certains debuggueurs pas trop cons donneront la valeur "enum" de la variable et non la valeur numérique).
Mais là aussi on est dans un cas de switch...case
-- Le travail n'est pas une bonne chose. Si ça l'était, les riches l'auraient accaparé
Pierre Maurette <maurettepierre@wanadoo.fr> écrivait :
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.
J'ai un contre exemple...
Une spécification de machine virtuelle donne les noms des opcodes et
leur valeur numérique. L'utilisation d'un enum en forçant les valeurs a
des avantages en terme de lisibilité, et de debug (certains debuggueurs
pas trop cons donneront la valeur "enum" de la variable et non la valeur
numérique).
Mais là aussi on est dans un cas de switch...case
--
Le travail n'est pas une bonne chose. Si ça l'était,
les riches l'auraient accaparé
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.
J'ai un contre exemple...
Une spécification de machine virtuelle donne les noms des opcodes et leur valeur numérique. L'utilisation d'un enum en forçant les valeurs a des avantages en terme de lisibilité, et de debug (certains debuggueurs pas trop cons donneront la valeur "enum" de la variable et non la valeur numérique).
Mais là aussi on est dans un cas de switch...case
-- Le travail n'est pas une bonne chose. Si ça l'était, les riches l'auraient accaparé
candide
Pierre Maurette a écrit :
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.
Ma question c'était d'abord pour lever une sorte d'ambiguité figurant dans la norme. En outre, et d'une façon générale, un code mal conçu au départ ou inadapté à ce en quoi on veut le tranformer peut devoir subir des transformations qui nient le bon sens donc il était toujours bon de savoir ce qu'on a le DROIT de faire.
Bien entendu, le compilateur est à mon sens autorisé à warner, et un lint quelconque à vous châtier.
Je n'ai jamais pratiqué lint (juste parce que je sens que la doc est mal faite et que je vais y passer x jours) mais pour ce qui est de gcc, ceci passe sans crier avec les options -Wextra -Wall -pedantic :
int main(void) { enum toto { A = 34, B = 51 } t = 28;
t++;
return 0; }
Les valeurs négatives sont valides dans les enum.
Dans les enum ? c'est imprécis. Les constantes énumérés ou une variable de ce type, ce n'est pas pareil, les constantes peuvent prendre des valeurs négatives maintenant que se passe-t-il si le type entier avec lequel le type énuméré est compatible est non signé ?
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.
Oui, c'est bien l'idée que je me fais d'un usage "normal" des enum.
- 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.
Là encore, ça correspond aussi à l'usage que je considèrerais comme "normal". Finalement, on n'utilise donc pas la possibilité qui est donnée de changer les valeur par défaut 0, 1, 2, ... des constantes d'énumération.
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.
C'est une construction qui t'est propre ou c'est largement pratiqué dans certains domaines que tu connais ?
Pierre Maurette a écrit :
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.
Ma question c'était d'abord pour lever une sorte d'ambiguité figurant
dans la norme. En outre, et d'une façon générale, un code mal conçu au
départ ou inadapté à ce en quoi on veut le tranformer peut devoir subir
des transformations qui nient le bon sens donc il était toujours bon de
savoir ce qu'on a le DROIT de faire.
Bien entendu, le compilateur est à mon sens autorisé à warner, et un
lint quelconque à vous châtier.
Je n'ai jamais pratiqué lint (juste parce que je sens que la doc est mal
faite et que je vais y passer x jours) mais pour ce qui est de gcc, ceci
passe sans crier avec les options -Wextra -Wall -pedantic :
int main(void)
{
enum toto { A = 34, B = 51 } t = 28;
t++;
return 0;
}
Les valeurs négatives sont valides dans les enum.
Dans les enum ? c'est imprécis. Les constantes énumérés ou une variable
de ce type, ce n'est pas pareil, les constantes peuvent prendre des
valeurs négatives maintenant que se passe-t-il si le type entier avec
lequel le type énuméré est compatible est non signé ?
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.
Oui, c'est bien l'idée que je me fais d'un usage "normal" des enum.
- 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.
Là encore, ça correspond aussi à l'usage que je considèrerais comme
"normal". Finalement, on n'utilise donc pas la possibilité qui est
donnée de changer les valeur par défaut 0, 1, 2, ... des constantes
d'énumération.
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.
C'est une construction qui t'est propre ou c'est largement pratiqué dans
certains domaines que tu connais ?
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.
Ma question c'était d'abord pour lever une sorte d'ambiguité figurant dans la norme. En outre, et d'une façon générale, un code mal conçu au départ ou inadapté à ce en quoi on veut le tranformer peut devoir subir des transformations qui nient le bon sens donc il était toujours bon de savoir ce qu'on a le DROIT de faire.
Bien entendu, le compilateur est à mon sens autorisé à warner, et un lint quelconque à vous châtier.
Je n'ai jamais pratiqué lint (juste parce que je sens que la doc est mal faite et que je vais y passer x jours) mais pour ce qui est de gcc, ceci passe sans crier avec les options -Wextra -Wall -pedantic :
int main(void) { enum toto { A = 34, B = 51 } t = 28;
t++;
return 0; }
Les valeurs négatives sont valides dans les enum.
Dans les enum ? c'est imprécis. Les constantes énumérés ou une variable de ce type, ce n'est pas pareil, les constantes peuvent prendre des valeurs négatives maintenant que se passe-t-il si le type entier avec lequel le type énuméré est compatible est non signé ?
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.
Oui, c'est bien l'idée que je me fais d'un usage "normal" des enum.
- 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.
Là encore, ça correspond aussi à l'usage que je considèrerais comme "normal". Finalement, on n'utilise donc pas la possibilité qui est donnée de changer les valeur par défaut 0, 1, 2, ... des constantes d'énumération.
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.
C'est une construction qui t'est propre ou c'est largement pratiqué dans certains domaines que tu connais ?
candide
Wykaaa a écrit :
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...
En fait, c'est pas tout à fait ça que je voulais écrire, plutôt :
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...
En fait, c'est pas tout à fait ça que je voulais écrire, plutôt :
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...
En fait, c'est pas tout à fait ça que je voulais écrire, plutôt :
Je n'ai pas la dernière édition (C99) mais avec le recul, d'un point de vue de la qualité des explications, c'est quand même le moins mauvais ouvrage sur le C que je connaisse (en plus, il est vivement recommandé sur clc).
Marc Boyer a écrit :
J'ai reçu le "C Programming: a modern approach" de K.N. KING,
et il paraît pas mal à première vue.
Oui, il a mis du temps à sortir, nous en avions parlé il y a trois ans :
Je n'ai pas la dernière édition (C99) mais avec le recul, d'un point de
vue de la qualité des explications, c'est quand même le moins mauvais
ouvrage sur le C que je connaisse (en plus, il est vivement recommandé
sur clc).
Je n'ai pas la dernière édition (C99) mais avec le recul, d'un point de vue de la qualité des explications, c'est quand même le moins mauvais ouvrage sur le C que je connaisse (en plus, il est vivement recommandé sur clc).
Pierre Maurette
candide, le 07/07/2008 a écrit :
Wykaaa a écrit :
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...
En fait, c'est pas tout à fait ça que je voulais écrire, plutôt :
ainsi qu'une auto-contrainte interdisant les opérateurs arithmétiques.
-- Pierre Maurette
candide, le 07/07/2008 a écrit :
Wykaaa a écrit :
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...
En fait, c'est pas tout à fait ça que je voulais écrire, plutôt :
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...
En fait, c'est pas tout à fait ça que je voulais écrire, plutôt :
ainsi qu'une auto-contrainte interdisant les opérateurs arithmétiques.
-- Pierre Maurette
Wykaaa
Marc Espie a écrit :
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).
Certes. C'est la fameuse "arithmétique pointeur" chère à K&R dont ils étaient si fière à leur début. L'équivalence t[i] avec *(t+i) montre en fait que les tableaux n'existent pas en C (en poussant le raisonnement à l'extrême).
C'est cable dans le langage. La seule facon de s'en passer, c'est de ne pas du tout utiliser de tableaux.
Oui.
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.
Bien sûr mais je pense qu'il est préférable d'écrire "void f(int[])" plutôt que "f(int *)" quand le paramètre sera utilisé comme un tableau dans la fonction.
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.
Ça c'est sûr, hélas !
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).
C'est ce que j'ai dit, autrement, dans une autre partie de ce fil.
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).
Je ne connais pas splint mais effectivement ça semble sympathique.
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.
Faire un langage à typage fort n'est déjà pas forcément facile en soi alors transformer un langage très faiblement typé comme le C en langage à typage fort est tâche quasi impossible...
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...
Même si C++ apparaît comme un langage très difficile à utiliser, même pour des programmeurs chevronnés, il est sûr que Stroustrup a réussi un joli "tour de force". Cependant, c'est au prix de quelques idiosyncrasies comme "delete [] t" lorsque t est un tableau d'instances, parce que, justement, il est impossible de distinguer un pointeur d'un tableau (l'équivalence pointeur/tableau du C nous revient en pleine gu**** !)
Marc Espie a écrit :
In article <4871ef49$0$934$ba4acef3@news.orange.fr>,
Wykaaa <wykaaa@yahoo.fr> 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).
Certes. C'est la fameuse "arithmétique pointeur" chère à K&R dont ils
étaient si fière à leur début. L'équivalence t[i] avec *(t+i) montre en
fait que les tableaux n'existent pas en C (en poussant le raisonnement à
l'extrême).
C'est cable dans le langage. La seule facon de s'en passer, c'est de ne
pas du tout utiliser de tableaux.
Oui.
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.
Bien sûr mais je pense qu'il est préférable d'écrire "void f(int[])"
plutôt que "f(int *)" quand le paramètre sera utilisé comme un tableau
dans la fonction.
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.
Ça c'est sûr, hélas !
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).
C'est ce que j'ai dit, autrement, dans une autre partie de ce fil.
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).
Je ne connais pas splint mais effectivement ça semble sympathique.
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.
Faire un langage à typage fort n'est déjà pas forcément facile en soi
alors transformer un langage très faiblement typé comme le C en langage
à typage fort est tâche quasi impossible...
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...
Même si C++ apparaît comme un langage très difficile à utiliser, même
pour des programmeurs chevronnés, il est sûr que Stroustrup a réussi un
joli "tour de force". Cependant, c'est au prix de quelques
idiosyncrasies comme "delete [] t" lorsque t est un tableau d'instances,
parce que, justement, il est impossible de distinguer un pointeur d'un
tableau (l'équivalence pointeur/tableau du C nous revient en pleine
gu**** !)
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).
Certes. C'est la fameuse "arithmétique pointeur" chère à K&R dont ils étaient si fière à leur début. L'équivalence t[i] avec *(t+i) montre en fait que les tableaux n'existent pas en C (en poussant le raisonnement à l'extrême).
C'est cable dans le langage. La seule facon de s'en passer, c'est de ne pas du tout utiliser de tableaux.
Oui.
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.
Bien sûr mais je pense qu'il est préférable d'écrire "void f(int[])" plutôt que "f(int *)" quand le paramètre sera utilisé comme un tableau dans la fonction.
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.
Ça c'est sûr, hélas !
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).
C'est ce que j'ai dit, autrement, dans une autre partie de ce fil.
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).
Je ne connais pas splint mais effectivement ça semble sympathique.
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.
Faire un langage à typage fort n'est déjà pas forcément facile en soi alors transformer un langage très faiblement typé comme le C en langage à typage fort est tâche quasi impossible...
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...
Même si C++ apparaît comme un langage très difficile à utiliser, même pour des programmeurs chevronnés, il est sûr que Stroustrup a réussi un joli "tour de force". Cependant, c'est au prix de quelques idiosyncrasies comme "delete [] t" lorsque t est un tableau d'instances, parce que, justement, il est impossible de distinguer un pointeur d'un tableau (l'équivalence pointeur/tableau du C nous revient en pleine gu**** !)
Wykaaa
Marc Espie a écrit :
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.
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.
- 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.
Bon OK, mais quand même...
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.
Le type enum améliorent la lisibilité du code et, donc, sa maintenabilité. Du point de vue sémantique, je suis d'accord que l'apport est nul. C'est un simple confort syntaxique comme le switch qui évite les cascades de "if...else" mais qui n'apporte rien à la programmation structurée.
Marc Espie a écrit :
In article <48714c38$0$904$ba4acef3@news.orange.fr>,
Wykaaa <wykaaa@yahoo.fr> 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.
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.
- 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.
Bon OK, mais quand même...
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.
Le type enum améliorent la lisibilité du code et, donc, sa
maintenabilité. Du point de vue sémantique, je suis d'accord que
l'apport est nul. C'est un simple confort syntaxique comme le switch qui
évite les cascades de "if...else" mais qui n'apporte rien à la
programmation structurée.
- 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.
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.
- 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.
Bon OK, mais quand même...
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.
Le type enum améliorent la lisibilité du code et, donc, sa maintenabilité. Du point de vue sémantique, je suis d'accord que l'apport est nul. C'est un simple confort syntaxique comme le switch qui évite les cascades de "if...else" mais qui n'apporte rien à la programmation structurée.
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...
En fait, c'est pas tout à fait ça que je voulais écrire, plutôt :
Ben non car il vaut mieux écrire (tant qu'à faire...) : enum booleen negation(enum booleen v) { return !v; /* ou return (enum booleen)(!(int)v) */ } avec : enum booleen {FAUX=0, VRAI=1} /* pour être sûr ! */
candide a écrit :
Wykaaa a écrit :
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...
En fait, c'est pas tout à fait ça que je voulais écrire, plutôt :
Ben non car il vaut mieux écrire (tant qu'à faire...) :
enum booleen negation(enum booleen v)
{
return !v; /* ou return (enum booleen)(!(int)v) */
}
avec :
enum booleen {FAUX=0, VRAI=1} /* pour être sûr ! */
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...
En fait, c'est pas tout à fait ça que je voulais écrire, plutôt :
Ben non car il vaut mieux écrire (tant qu'à faire...) : enum booleen negation(enum booleen v) { return !v; /* ou return (enum booleen)(!(int)v) */ } avec : enum booleen {FAUX=0, VRAI=1} /* pour être sûr ! */