Pourquoi le compilateur ne détecte-t-il pas et ne génère-t-il pas d’erreurs lors d’une tentative de modification de littéraux char *?

Supposons les deux morceaux de code suivants:

char *c = "hello world"; c[1] = 'y'; 

Celui ci-dessus ne fonctionne pas.

 char c[] = "hello world"; c[1] = 'y'; 

Celui-ci fait.

En ce qui concerne le premier, je comprends que la chaîne “hello world” peut être stockée dans la section de mémoire en lecture seule et ne peut donc pas être modifiée. Le second crée cependant un tableau de caractères sur la stack et peut donc être modifié.

Ma question est la suivante: pourquoi les compilateurs ne détectent-ils pas le premier type d’erreur? Pourquoi cela ne fait-il pas partie de la norme C? Y a-t-il une raison particulière à cela?

Les compilateurs C ne sont pas obligés de détecter la première erreur car les littéraux de chaîne C ne sont pas const .

En référence au projet de norme N1256 de la norme C99 :

6.4.5 paragraphe 5:

Dans la phase de traduction 7, un octet ou un code de valeur zéro est ajouté à chaque séquence de caractères multi-octets résultant d’un littéral de chaîne ou de littéraux. La séquence de caractères multi-octets est ensuite utilisée pour initialiser un tableau de durée et de longueur de stockage statique juste suffisante pour contenir la séquence. Pour les littéraux de chaîne de caractères, les éléments du tableau ont le type char et sont initialisés avec les octets individuels de la séquence de caractères multi-octets; […]

Paragraphe 6:

Il n’est pas précisé si ces tableaux sont distincts à condition que leurs éléments aient les valeurs appropriées. Si le programme tente de modifier un tel tableau, le comportement n’est pas défini.

(C11 ne change pas cela.)

Ainsi, la chaîne littérale "hello, world" est de type char[13] ( pas const char[13] ), qui est converti en char* dans la plupart des contextes.

Tenter de modifier un object const a un comportement indéfini, et la plupart du code qui tente de le faire doit être diagnostiqué par le compilateur (vous pouvez contourner ce problème avec un transtypage, par exemple). Tenter de modifier un littéral de chaîne a également un comportement indéfini, mais pas parce que c’est const (ce n’est pas le cas); c’est parce que la norme dit spécifiquement que le comportement est indéfini.

Par exemple, ce programme est ssortingctement conforme:

 #include  void print_ssortingng(char *s) { printf("%s\n", s); } int main(void) { print_ssortingng("Hello, world"); return 0; } 

Si les littéraux de chaîne étaient const , le passage de "Hello, world" à une fonction prenant un caractère (non const ) char* nécessiterait un diagnostic. Le programme est valide, mais il afficherait un comportement non défini si print_ssortingng() tentait de modifier la chaîne indiquée par s .

La raison est historique. Pre-ANSI C n’avait pas le mot-clé const , il n’existait donc aucun moyen de définir une fonction prenant un caractère char* et promettant de ne pas modifier son object. Rendre constants de chaîne de caractères constants dans ANSI C (1989) aurait brisé le code existant, et il n’a pas été possible d’apporter une telle modification dans les éditions ultérieures de la norme.

Les -Wwrite-ssortingngs de gcc lui -Wwrite-ssortingngs de traiter les littéraux de chaîne comme const , mais font de gcc un compilateur non conforme, car il n’émet pas de diagnostic pour ceci:

 const char (*p)[6] = &"hello"; 

( "hello" est de type char[6] , donc &"hello" est de type char (*)[6] , ce qui est incompatible avec le type déclaré de p . Avec -Wwrite-ssortingngs , &"hello" est traité comme étant de type const char (*)[6] .) C’est probablement pour cela que ni -Wextra ni -Wextra n’inclut -Wwrite-ssortingngs .

D’autre part, le code qui déclenche un avertissement avec -Wwrite-ssortingngs devrait être corrigé de toute façon. Ce n’est pas une mauvaise idée d’écrire votre code C pour qu’il soit compilé sans diagnostics avec et sans -Wwrite-ssortingngs .

(Notez que les littéraux de chaîne C ++ sont const , car lorsque Bjarne Stroustrup était en train de concevoir C ++, il n’était pas aussi soucieux de la compatibilité ssortingcte du code C ancien.)

Les compilateurs peuvent détecter la première “erreur”.

Dans les versions modernes de gcc, si vous utilisez -Wwrite-ssortingngs, vous obtenez un message indiquant que vous ne pouvez pas affecter const char* à char* . Cet avertissement est activé par défaut pour le code C ++.

C’est là que réside le problème – la première affectation, pas le bit c[1] = 'y' . Bien sûr, il est légal de prendre un caractère char* , de le déréférencer et de l’atsortingbuer à l’adresse déréférencée.

Citant l’ man 1 gcc :

 When compiling C, give ssortingng constants the type "const char[length]" so that copying the address of one into a non-"const" "char *" pointer will get a warning. These warnings will help you find at comstack time code that can try to write into a ssortingng constant, but only if you have been very careful about using "const" in declarations and prototypes. Otherwise, it will just be a nuisance. This is why we did not make -Wall request these warnings. 

Donc, fondamentalement, comme la plupart des programmeurs n’ont pas écrit de code const-correct au début du langage C, ce comportement n’est pas celui par défaut pour gcc. Mais c’est pour g ++.

-Wwrite-ssortingngs semble faire ce que vous voulez. J’aurais pu jurer que cela faisait partie de -Wall .

 % cat chars.c #include  int main() { char *c = "hello world"; c[1] = 'y'; return 0; } % gcc -Wall -o chars chars.c % gcc -Wwrite-ssortingngs -o chars chars.c chars.c: In function 'main': chars.c:5: warning: initialization discards qualifiers from pointer target type 

À partir des pages de manuel:

Lors de la compilation de C, donnez aux constantes de chaîne le type “const char [longueur]” afin que la copie de l’adresse de l’une dans un pointeur non “const” “char *” obtienne un avertissement. Ces avertissements vous aideront à trouver, lors de la compilation, un code pouvant essayer d’écrire dans une constante de chaîne, mais uniquement si vous avez fait très attention à l’utilisation de “const” dans les déclarations et les prototypes. Sinon, ce sera juste une nuisance. C’est pourquoi nous n’avons pas demandé à ces avertissements de s’exécuter.

Lors de la compilation de C ++, avertissez de la conversion déconseillée des littéraux de chaîne en “char *”. Cet avertissement est activé par défaut pour les programmes C ++.

Notez que “activé par défaut pour C ++” est probablement la raison pour laquelle je (et d’autres) pense à -Wall couvre. Notez également l’explication de la raison pour laquelle il ne fait pas partie de -Wall .

En ce qui concerne la norme, C99 , 6.4.5, point 6 (page 63 du PDF lié) se lit comme suit:

Il n’est pas précisé si ces tableaux sont distincts à condition que leurs éléments aient les valeurs appropriées. Si le programme tente de modifier un tel tableau, le comportement n’est pas défini.

char* c = strdup("..."); rendrait c[1] sensible. ( Suppression supprimée sur C ) Bien qu’un compilateur intelligent puisse / prévienne contre cela, C est traditionnellement proche de la machine, sans vérification (bounds / format / …) et autres frais supplémentaires “inutiles”.

lint est l’outil permettant de détecter de telles erreurs: le fait qu’un const char* été affecté à un char* . Cela marquerait aussi un caractère char c = c[30]; (Non plus dépendant du type, mais aussi erreur d’adressage.) Comme il serait bien d’avoir déclaré c comme const char* . C est une langue plus ancienne avec une tradition de clémence et fonctionnant sur de nombreuses plateformes.