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

compilateur trop pointilleu ?

2 réponses
Avatar
PIGUET Bruno
Bonjour à tous,


Soit l'ECM suivant :
-----debut_citation----------
#include <stdio.h>

void
f_cst (const char *const content[], size_t nb)
{
size_t i;
for (i = 0; i < nb; i++)
{
fputs (content[i], stdout);
fputs ("\n", stdout);
}
}

void
f_var (char * content[], size_t nb)
{
size_t i;
for (i = 0; i < nb; i++)
{
content[i] = "zz";
}
}

int
main ()
{
const char *tab1[] = { "a", "b", "c" };
char *tab2[] = { "1", "2", "3" };

/* cas normal */
f_cst (tab1, sizeof (tab1) / sizeof (const char *));
/* Le compilateur va râler. Mais où est le danger ? */
f_cst (tab2, sizeof (tab2) / sizeof (char *));
/* un cast permet de rassurer le compilateur */
f_cst ((const char * const *)tab2, sizeof (tab2) / sizeof (char *));

/* cas normal */
f_var (tab2, sizeof (tab2) / sizeof (char *));
/* Le compilateur va râler. Il a raison : cette fonction va modifier
tab1 */
f_var (tab1, sizeof (tab1) / sizeof (char *));
/* un cast permet de passer outre : gare au résultat */
f_var ((char **)tab1, sizeof (tab2) / sizeof (char *));

/* Pour vérifier que tab1 a bien été modifié */
f_cst (tab1, sizeof (tab1) / sizeof (const char *));

return 0;
}
-----fin_citation----------

Je le compile avec gcc (4.2.3) :
gcc -ansi -Wall -Wextra essai_const_char_arg.c -o essai_const_char_arg

J'ai les messages d'erreurs annoncés par les commentaires des lignes
32 et 39 :
essai_const_char_arg.c:33: attention : passing argument 1 of ‘f_cst’ from
incompatible pointer type

Je ne comprend pas pourquoi j'ai le même message d'erreur sur ces 2
lignes.
En ligne 33, je passe un simple "char *tab2[]" à une fonction qui
travaille sur un "const char *const content[]"
J'ai tendance à penser qu'il n'y a pas gros risque : la fonction ne va
rien modifier dans mon tableau.

En ligne 40, c'est l'inverse : je passe un "const char *tab1[]" à
une fonction qui travaille sur un "char * content[]" ... et qui
va le modifier. Je perd donc le caractère "const", il est normal que
le compilateur râle.

Dans le cas "pas dangereux", je peux faire taire le compilateur par un
cas (ligne 35). Mais je n'aime pas bien ça, car le même genre de technique
utilisé ligne 42 peut avoir beaucoup plus de conséquences fâcheuses.

Pour le dire autrement : dans un "vrai" code, il ne faut pas faire le
cast de la ligne 42, et je n'aimerais donc pas être obligé de faire le
cast de la ligne 35, qui y ressemble trop.

J'ai sans doute loupé qqchose. Quel peut être le risque qui justifie le
message d'erreur de la ligne 33 ?
Ou alors quelle est la "bonne" syntaxe pour indiquer qu'une fonction ne
va rien rien modifier dans le tableau de chaînes de caractères qu'on
lui passe en argument ?

Merci,

Bruno.
PS :
Ajouter "-Wno-write-strings " sur la ligne de compilation ne change rien.

2 réponses

Avatar
Antoine Leca
Le 17/04/2009 13:38, Bruno PIGUET écrivit :
void f_cst (const char *const content[], size_t nb);
const char *tab1[] = { "a", "b", "c" };
char *tab2[] = { "1", "2", "3" };

/* cas normal */
f_cst (tab1, sizeof (tab1) / sizeof (const char *));
/* Le compilateur va râler. Mais où est le danger ? */
f_cst (tab2, sizeof (tab2) / sizeof (char *));



Si tu écris à plat l'appel, tu réalises l'opération
content = tab2;
Autrement dit, tu assignes un tableau de chaînes non qualifiées (tab2) à
un tableau de chaînes qualifiées «const» (content). Les règles de C sont
de râler, parce que cela passe outre la qualification.

Est-ce un comportement trop simpliste ? C'est possible.
Mais bon, C est un langage assez simpliste, à la base.

Tu peux aussi aller voir du côté des diatribes de DMR concernant noalias
et const, tu verras que tu n'es pas tout-à-fait le seul à considérer que
const en C n'est pas parfait; d'un autre côté, on peut aussi considérer
que const a *aussi* apporté beaucoup de choses positives au C, et qu'à
tout prendre c'est mieux que rien.


Je ne comprend pas pourquoi j'ai le même message d'erreur sur ces 2
lignes.



Parce que le compilateur que tu utilises est trop simpliste, et
n'analyse pas en profondeur l'effet du qualificatif const, il applique
bêtement les règles, qui disent : si destination et origine pas
qualifiées pareil, pas qosher^Wcompatible ; détail: C90 6.5.3p7, C99
6.7.3p9).
Il y a une exception possible pour le qualificatif le plus extérieur
(celui de droite) dans les règles d'assignation, ce qui fait que pour
tab1 le compilo ne râle pas (alors que tu assignes un char const** à un
char const*const*); mais cette règle n'est pas transitive.

Une explication possible du pourquoi du comment pourrait être une
implémentation où les chaînes constantes seraient rangées dans un espace
différent (appelons-le C) des chaînes modifiables (que nous appelerons
M) ; dans le cas tab1, tout va bien, tab1 est dans l'espace M mais les
adresses qu'il contient pointent vers l'espace C; au moment de faire
l'appel le compilo va recopier tab1 dans l'espace C, pour satisfaire la
constrainte de ce qu'attend f_cst.
Avec tab2, le tableau tab2 se trouve aussi dans l'espace M mais les
chaînes pointées elles aussi sont dans l'espace M; pour que cela puisse
fonctionner pour tab2, en plus de devoir copier tab1, le compilo devrait
alors copier chacune des chaînes pointées vers l'espace C, et là c'est
trop demander...


Ou alors quelle est la "bonne" syntaxe pour indiquer qu'une fonction ne
va rien rien modifier dans le tableau de chaînes de caractères qu'on
lui passe en argument ?



Changer de langage.


Antoine
Avatar
Alexandre Bacquart
PIGUET Bruno wrote:
const char *tab1[] = { "a", "b", "c" };


> ...

Antoine explique assez bien, pas la peine d'insister. Par contre :

sizeof (tab1) / sizeof (const char *)

J'ai déjà eu à regretter ce genre de choses, presque aussi dangereuses
qu'un cast. Il est possible de remplacer par :

sizeof (tab1) / sizeof (*tab1)

Ca évite des surprises si jamais le type change...



--
Alex