char const * const argv[] vs char const *argv[] vs char* argv[]

Le
Marc Boyer
Je crossposte sur les deux forums C et C++ car le code est
semble dans l'intersection dans languages, meme si la semantique
est peut-etre differente. Que chacun reponde dans le(s) forum(s)
appropries a sa reponse.

Un warning de mon compilateur (gcc 3.3) me fait douter de ma
comprehension de const

Si je passe un pointeur sur des char a une fonction qui attend
un pointeur sur des char constant, tout va bien (gcc et g++).

Mais quand j'attaque les pointeurs de pointeur (manip de argv),
ca se corse.

Ni gcc ni g++ n'acceptent la conversion char* argv[] en const char* argv[].
Je comprends qu'avec un pointeur de pointeur de char, on peut modifier
l'argument, dans le sens ou on fait pointeur sur "autre chose", mais bon,
si je veux interdire une modif de l'argument, j'ecris
char const * const argv[]
donc deja, je comprends pas trop

Ensuite, quand je tente la signature char const * const argv[], gcc
continue a raler, mais g++ ne dit plus rien.

Voici un code illustratif de mon incomprehension

void foo(const char* string);
void barConst(char const * argv[]);
void barConstConst(char const * const argv[]);

int main(int argc, char* argv[]){
char aString[10];
aString[0]= 0;
foo(aString); // OK pour gcc et g++
barConst(argv); // gcc "type pointeur incompatible"
// g++ "conversion invalide de"
barConstConst(argv); // gcc "type pointeur incompatible"
// g++ OK
return 0;
}


Marc Boyer
--
La contractualisation de la recherche, c'est me donner de l'argent pour
faire ce que je ne sais pas faire, que je fais donc mal, pendant que ce
que je sais faire, je le fais sans moyens
Vos réponses Page 1 / 2
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Antoine Leca
Le #618217
En c65d17$7hd$, Marc Boyer va escriure:
Ni gcc ni g++ n'acceptent la conversion char* argv[] en const char*
argv[].


La déclaration "correcte" est
char * const argv[]

<DIGRESSION>
Pour des raisons de compatibilité avec l'existant, l'officielle n'a pas le
const. Mais la norme C dans son texte refuse l'autorisation de modifier les
éléments du tableau: 5.1.2.2.1p2, en partie:

-- The parameters *argc* and *argv* and the strings pointed to by the
*argv* array shall be modifiable by the program, and retain their last-
stored values between program startup and program termination.

Par élimination, tout est modifiable, sauf les éléments du tableau argv
original. Autrement sit, si tu veux modifier les éléments de argv[], il faut
d'arbord copier le tableau ailleurs, faire les modifications que tu veux, et
ensuite réaffecter argv vers ta copie locale.
</DIGRESSION>

GCC C (et probablement GCC C++), dans son infinie grandeur, te laisse sans
broncher faire des conversions de argv vers un char *[], pour la même raison
que la norme: compatibilité avec l'existant qui ne coannaitr pas les
bienfaits de const. Mais à partir du moment où il y a un const dans le type,
GCC C devient féroce, et refuse de convertir un tableau non modifiable
(argv) en tableau modifiable (ton paramètre), même si dans le même temps tu
augmentes la contrainte sur les éléments pointés (modifiables selon la
norme, non modifiés selon ton prototype).

D'autre part, il y a la tentative d'augmenter la contrainte sur les chaînes
pointées. Ici, je pense que C et C++ divergent, donc tout ce que j'écris
peut ne pas être vrai en C++.

En C, donc, la règle sur const signifiant "ne sera pas modifié" s'applique
uniquement au paramètre lui-même, pas aux composants à partir desquels est
construit son type.

Détail: 6.3.2.2/6.5.2.2 Function calls, par. 2, en partie:
Each argument shall have a type such that its value may be assigned to
an object with the unqualified version of the type of its corresponding
parameter.

Ce qui renvoie vers les affectations: 6.3.16.1/6.5.16.1 Simple assignment,
par.1, en partie:
- both operands are pointers to qualified or unqualified versions of
compatible types, and the type pointed to by the left has all the
qualifiers of the type pointed to by the right;

Il faut donc s'en remettre aux règles de compatibilité de types pour voir si
on peut ajouter des qualificateurs sur les sous-éléments. Et là, surprise,
si tu changes les qualificateurs, ce n'est plus compatible: 6.5.4.1/6.7.5.1
Pointer declarators, par.2:
For two pointer types to be compatible, both shall be identically
qualified and both shall be pointers to compatible types.

Ce qui explique les râleries de GCC C.


Antoine

Jean-Marc Bourguet
Le #618216
Je ne suis pas sur de ce qu'est ton probleme (en particulier prends tu
argc/argv pour l'illustrer ou bien les parametres de main sont parties
du problemes?). Si c'est simplement comme illustration du probleme de
conversion entre types pointeurs de pointeurs avec des qualifications
const/volatile differentes:

Les regles C++ telles que je m'en souviens sont:

- si a un niveau, la source a un const, alors la destination doit
l'avoir aussi; de meme pour volatile

- si a un niveau, la destination a un const et la source pas, alors
la destination doit avoir un const a tous les niveaux precedants

Exemple de ce que la deuxieme regle veut eviter:

int main() {
char const c = 'c';
char* pc;
char const** pcc = &pc; // interdit
*pcc = &c;
*pc = 'C'; // modifie c
}

(suivi sur fr.comp.lang.c++, je ne traite que du C++ et ne sais pas si
les regles sont differentes en C)

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Marc Boyer
Le #618215
In article
En c65d17$7hd$, Marc Boyer va escriure:
Ni gcc ni g++ n'acceptent la conversion char* argv[] en const char*
argv[].


La déclaration "correcte" est
char * const argv[]


Dans le cas qui motive mon questionnement, je reproduis les memes
messages d'erreur en subsituant toto a main dans le code.
Mais je saute quand meme sur la digression.

<DIGRESSION>
Pour des raisons de compatibilité avec l'existant, l'officielle n'a pas le
const. Mais la norme C dans son texte refuse l'autorisation de modifier les
éléments du tableau: 5.1.2.2.1p2, en partie:

-- The parameters *argc* and *argv* and the strings pointed to by the
*argv* array shall be modifiable by the program, and retain their last-
stored values between program startup and program termination.

Par élimination, tout est modifiable, sauf les éléments du tableau argv
original. Autrement sit, si tu veux modifier les éléments de argv[], il faut
d'arbord copier le tableau ailleurs, faire les modifications que tu veux, et
ensuite réaffecter argv vers ta copie locale.


En fait, dans l'usage immediat que j'en vois, ca permet de modifier l'ordre
des parametres, pour, par exemple, mettre les options au debut de la ligne
de commande et se faciliter l'analyse).

</DIGRESSION>

[SNIP]

Mais à partir du moment où il y a un const dans le type,
GCC C devient féroce, et refuse de convertir un tableau non modifiable
(argv) en tableau modifiable (ton paramètre), même si dans le même temps tu
augmentes la contrainte sur les éléments pointés (modifiables selon la
norme, non modifiés selon ton prototype).


Sauf que meme si j'ecris
int toto(int argc, char* argv[])
c'est pareil ;-0

D'autre part, il y a la tentative d'augmenter la contrainte sur les chaînes
pointées. Ici, je pense que C et C++ divergent, donc tout ce que j'écris
peut ne pas être vrai en C++.


Je sens en effet des divergences (ce qui m'a pousse a un crosspost,
m'obligeant a forcer la main a mon lecteur de news)

En C, donc, la règle sur const signifiant "ne sera pas modifié" s'applique
uniquement au paramètre lui-même, pas aux composants à partir desquels est
construit son type.


La, je seche... Prenons
void foo(const char* s)
qu'est-ce que le parametre ( s ?) et son type c'est char* ou const char* ?

Détail: 6.3.2.2/6.5.2.2 Function calls, par. 2, en partie:
Each argument shall have a type such that its value may be assigned to
an object with the unqualified version of the type of its corresponding
parameter.


unqualified == pas de const ?
si le type c'est char const * const, le unqualified, c'est
char const * ou char* ?

Plus je relis les paragraohes que tu cites, plus ca me semble
crucial dans la comprehension.

Ce qui renvoie vers les affectations: 6.3.16.1/6.5.16.1 Simple assignment,
par.1, en partie:
- both operands are pointers to qualified or unqualified versions of
compatible types, and the type pointed to by the left has all the
qualifiers of the type pointed to by the right;

Il faut donc s'en remettre aux règles de compatibilité de types pour voir si
on peut ajouter des qualificateurs sur les sous-éléments. Et là, surprise,
si tu changes les qualificateurs, ce n'est plus compatible: 6.5.4.1/6.7.5.1
Pointer declarators, par.2:
For two pointer types to be compatible, both shall be identically
qualified and both shall be pointers to compatible types.

Ce qui explique les râleries de GCC C.


Heuh... J'ai pas tout saisi la...

Marc Boyer
--
La contractualisation de la recherche, c'est me donner de l'argent pour
faire ce que je ne sais pas faire, que je fais donc mal, pendant que ce
que je sais faire, je le fais sans moyens...


Horst Kraemer
Le #618001
On 21 Apr 2004 08:57:11 GMT, Marc Boyer

Ni gcc ni g++ n'acceptent la conversion char* argv[] en const char* argv[].
Je comprends qu'avec un pointeur de pointeur de char, on peut modifier
l'argument, dans le sens ou on fait pointeur sur "autre chose", mais bon,
si je veux interdire une modif de l'argument, j'ecris
char const * const argv[]
donc deja, je comprends pas trop...


Je ne comprends plus ce que tu ne comprends pas ;-)

Il n'y pas de conversion automatique char** vers const char** parce
que la norme le dit ;-) On en a déjà parlé ici plusieurs fois.


Un exemple qui montre qu'une conversion automatique ne serait pas sure

const char *cc = "hello world!";

void foo(const char **pp)
{
*pp = cc;
}

char *p;
foo(&p); /* si c'était légal on pourrait */
p[0]="H"; /* dévier un char* sur une chaîne constante. */

...oops.....


--
Horst

Antoine Leca
Le #618000
En fait, au début j'ai répondu pensant à un problème lié à argv, où GCC
serait intelligent. Et ensuite je me suis aperçu que le problème était plus
de fond, en rien spécifique à argv. J'ai laissé la digression, que je
trouvais intéressante (chacun ses goûts ;-)), mais cela a dû t'égarer plus
qu'autre chose. Désolé.

En c6627l$bu7$, Marc Boyer va escriure:
In article
En c65d17$7hd$, Marc Boyer va escriure:
Ni gcc ni g++ n'acceptent la conversion char* argv[] en const char*
argv[].


La déclaration "correcte" est
char * const argv[]

Autrement [d]it, si tu veux modifier les éléments de
argv[], il faut d'arbord copier le tableau ailleurs, faire les
modifications que tu veux, et ensuite réaffecter argv vers ta copie
locale.


En fait, dans l'usage immediat que j'en vois, ca permet de modifier
l'ordre des parametres, pour, par exemple, mettre les options au
debut de la ligne de commande et se faciliter l'analyse).


Voui. Et pas le droit de faire cela salement directement dans argv (genre
appeler qsort).


</DIGRESSION>
<couic>


En C, donc, la règle sur const signifiant "ne sera pas modifié"
s'applique uniquement au paramètre lui-même, pas aux composants à
partir desquels est construit son type.



Attention, il y a trois niveaux (ou plus).

Le premier const (à partir de la gauche, donc à droite du *), saute
automatiquement, puisqu'il faut bien passer l'argument (il s'applique dans
la définition de la fonction). C'est le 6.3.2.2/6.5.2.2 ci-dessous.

Le deuxième const saute en vertu d'une règle spéciale, destinée si je me
rappelle bien à faciliter la compatibilité et l'utilisabilité de const, en
particulier la croyance que « const * pour un paramètre signifie que la
fonction ne modifie pas l'argument », une utile manière de voir les choses
mais la norme n'est pas formulée de cette manière.

Les autres const... ne sautent pas ! Ils restent ! Et ils bloquent.


La, je seche... Prenons
void foo(const char* s)
qu'est-ce que le parametre ( s ?)


Oui.

et son type c'est char* ou const char* ?


const char *
Donc ici, on n'a que deux niveaux, ce n'est pas « intéressant ». ;-)


Détail: 6.3.2.2/6.5.2.2 Function calls, par. 2, en partie:
Each argument shall have a type such that its value may be
assigned to an object with the unqualified version of the type
of its corresponding parameter.


unqualified == pas de const ?


Oui, mais uniquement pour le type du paramètre, _pas_ pour le type vers
lequel ce type pointe.

si le type c'est char const * const, le unqualified, c'est
char const * ou char* ?


char const *

Et dans tes exemples initiaux,
void barConst(char const * argv[]);
le type de argv, c'est char const * * (le type tableau est réécrit en
pointeur).
void barConstConst(char const * const argv[]);
Ici, le type de argv, c'est char const * const *.

Ce qui renvoie vers les affectations: 6.3.16.1/6.5.16.1 Simple
assignment, par.1, en partie:
- both operands are pointers to qualified or unqualified
versions of compatible types, and the type pointed to by the
left has all the qualifiers of the type pointed to by the right;



C'est là qu'est le "truc". En vertu de cette règle, le const du s de ton
exemple foo « saute ». Mais pas celui de argv (dans les deux cas), c'est
char const * *. Alors que argv et toto, je le rappelle, sont de type char *
[], qui passe à char * * pour l'affectation.


Il faut donc s'en remettre aux règles de compatibilité de types pour
voir si on peut ajouter des qualificateurs sur les sous-éléments. Et
là, surprise, si tu changes les qualificateurs, ce n'est plus
compatible: 6.5.4.1/6.7.5.1 Pointer declarators, par.2:
For two pointer types to be compatible, both shall be identically
qualified and both shall be pointers to compatible types.

Ce qui explique les râleries de GCC C.


Heuh... J'ai pas tout saisi la...


Ça va mieux, maintenant ?


Antoine



Marc Boyer
Le #617999
Horst Kraemer wrote:
On 21 Apr 2004 08:57:11 GMT, Marc Boyer

Il n'y pas de conversion automatique char** vers const char** parce
que la norme le dit ;-) On en a déjà parlé ici plusieurs fois.


Oui, mais j'avais piscine ;-)

Un exemple qui montre qu'une conversion automatique ne serait pas sure

const char *cc = "hello world!";

void foo(const char **pp)
{
*pp = cc;
}

char *p;
foo(&p); /* si c'était légal on pourrait */
p[0]="H"; /* dévier un char* sur une chaîne constante. */

...oops.....


OK, je note, j'imprime, je sauve et je le me tatoue dans la main.

Ceci etant dit, la conversion char** vers char const * const *
elle serait sure dans ce cas (enfin, il me semble).
D'ailleurs, g++ semble l'accepter.
J'ai rate un autre truc ?

Marc Boyer
--
La contractualisation de la recherche, c'est me donner de l'argent pour
faire ce que je ne sais pas faire, que je fais donc mal, pendant que ce
que je sais faire, je le fais sans moyens...

Jean-Marc Bourguet
Le #617998
Marc Boyer
Ceci etant dit, la conversion char** vers char const * const *
elle serait sure dans ce cas (enfin, il me semble).
D'ailleurs, g++ semble l'accepter.
J'ai rate un autre truc ?


g++ l'accepte parce que les regles du C++ sont differentes des regles
du C (voir plus simples mais interdisent des cas ou la conversion est sans
probleme.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Marc Boyer
Le #735930
Jean-Marc Bourguet wrote:
Les regles C++ telles que je m'en souviens sont:

- si a un niveau, la source a un const, alors la destination doit
l'avoir aussi; de meme pour volatile

- si a un niveau, la destination a un const et la source pas, alors
la destination doit avoir un const a tous les niveaux precedants


Oui, ok, la notion de précédence étant sur les niveaux
d'indirection. OK.

Exemple de ce que la deuxieme regle veut eviter:

int main() {
char const c = 'c';
char* pc;
char const** pcc = &pc; // interdit
*pcc = &c;
*pc = 'C'; // modifie c
}


OK.

Marc Boyer
--
La contractualisation de la recherche, c'est me donner de l'argent pour
faire ce que je ne sais pas faire, que je fais donc mal, pendant que ce
que je sais faire, je le fais sans moyens...

Marc Boyer
Le #617996
Antoine Leca wrote:
En fait, au début j'ai répondu pensant à un problème lié à argv, où GCC
serait intelligent. Et ensuite je me suis aperçu que le problème était plus
de fond, en rien spécifique à argv. J'ai laissé la digression, que je
trouvais intéressante (chacun ses goûts ;-)), mais cela a dû t'égarer plus
qu'autre chose. Désolé.


Heureusement, j'ai vite eut l'idée de changer main en toto ;-)

Attention, il y a trois niveaux (ou plus).

Le premier const (à partir de la gauche, donc à droite du *), saute
automatiquement, puisqu'il faut bien passer l'argument (il s'applique dans
la définition de la fonction). C'est le 6.3.2.2/6.5.2.2 ci-dessous.


Je comprends bien pourquoi (même s'il va peut-être falloir
que je relise le paragraphe pour comprendre comment il dit ça).

Le deuxième const saute en vertu d'une règle spéciale, destinée si je me
rappelle bien à faciliter la compatibilité et l'utilisabilité de const, en
particulier la croyance que « const * pour un paramètre signifie que la
fonction ne modifie pas l'argument », une utile manière de voir les choses
mais la norme n'est pas formulée de cette manière.


J'avoue avoir présenté le const à mes élèves justement
avec cette idée de non-modification.
Je me demande que leur dire maintenant que le cours
s'attarde sur les pointeurs...

Détail: 6.3.2.2/6.5.2.2 Function calls, par. 2, en partie:
Each argument shall have a type such that its value may be
assigned to an object with the unqualified version of the type
of its corresponding parameter.


unqualified == pas de const ?


Oui, mais uniquement pour le type du paramètre, _pas_ pour le type vers
lequel ce type pointe.

si le type c'est char const * const, le unqualified, c'est
char const * ou char* ?


char const *


OK, on vire le const le plus proche du parametre.

Ça va mieux, maintenant ?


Oui, mais j'hésite sur la manière de présenter la chose...
Le coup de "le deuxième const saute pour faire comme
si le const signifiait 'ne modifie pas son paramètre'", je
le sens mal...
En gros, la règle c'est:
- le premier const saute (c'est de toute façon une copie)
- le second, on accepte la conversion non const->const,
pour dire qu'une fonction ne modifie pas son paramètre
- pour les autres, il faut égalité parce que c'est comme
ça (je peux montrer le contre exemple qui m'a été fournit
sans m'attarder sur le fait qu'en rajoutant un cast de
plus, ce serait sans danger mais refusé quand même)...

Enseigner, c'est devoir expliquer des trucs qu'on se
gardait bien d'aborder avant...

Marc Boyer
--
La contractualisation de la recherche, c'est me donner de l'argent pour
faire ce que je ne sais pas faire, que je fais donc mal, pendant que ce
que je sais faire, je le fais sans moyens...



Antoine Leca
Le #617994
En c6685m$dbs$, Marc Boyer va escriure:
Antoine Leca wrote:
Le deuxième const saute en vertu d'une règle spéciale, destinée si
je me rappelle bien à faciliter la compatibilité et l'utilisabilité
de const, en particulier la croyance que « const * pour un paramètre
signifie que la fonction ne modifie pas l'argument », une utile
manière de voir les choses mais la norme n'est pas formulée de cette
manière.


J'avoue avoir présenté le const à mes élèves justement
avec cette idée de non-modification.
Je me demande que leur dire maintenant que le cours
s'attarde sur les pointeurs...


Rien de plus, de toute manière tout le monde utilise const pour cela, ce
n'est pas une mauvaise notion. C'est juste que la norme (C) ne dit pas
exactement cela, et si tu rentres dans ce genre de détails...

Déjà, tes élèves, pour piper ce sujet, il faut qu'ils aient assimilé les
tableaux qui s'amuissent en pointeurs, et qu'ils sachent lire de suite le
nombre de niveaux de pointeurs.


Oui, mais j'hésite sur la manière de présenter la chose...
Le coup de "le deuxième const saute pour faire comme
si le const signifiait 'ne modifie pas son paramètre'", je
le sens mal...
En gros, la règle c'est:
- le premier const saute (c'est de toute façon une copie)
- le second, on accepte la conversion non const->const,
pour dire qu'une fonction ne modifie pas son paramètre
- pour les autres, il faut égalité parce que c'est comme
ça


Oui, c'est bien comme cela qu'a été rédigé la norme: ceux qui l'ont rédigé
n'étaient pas convaincus, mais ils sentaient qu'ils devaient faire comme
cela... cf. (Dave Prosser étant celui qui a rédigé la norme ANSI). Les exemples
démontrant les problèmes concrets sont venus plus tard.

(je peux montrer le contre exemple qui m'a été fournit
sans m'attarder sur le fait qu'en rajoutant un cast de
plus, ce serait sans danger mais refusé quand même)...


Comme l'explique Jean-Marc ailleurs, certaines choses « sûres » sont
refusées par les règles C (et pas par les règles C++), et en contrepartie
les règles C sont moins complexes à écrire. Au résultat, dans ces cas-là, ce
déclenche un diagnostic en C, mais il n'y a pas de problèmes: le
comportement est clair, sera celui attendu, et les bons compilateurs ne vont
pas être suffisament idiots pour refuser de compiler le programme.


Antoine


Publicité
Poster une réponse
Anonyme