Pourquoi est-ce que j'obtiens une erreur de segmentation lors de l'écriture dans une chaîne?
C99 N1256 draft
Il existe deux utilisations différentes des littéraux de chaîne de caractères:
Initialiser char[]
:
char c[] = "abc";
C'est "plus magique", et décrit au 6.7.8 / 14 "Initialisation":
Un tableau de type caractère peut être initialisé par une chaîne de caractères littérale, éventuellement entourée d'accolades. Les caractères successifs du littéral de chaîne de caractères (y compris le caractère nul de fin s'il y a de la place ou si le tableau est de taille inconnue) initialisent les éléments du tableau.
Ce n'est donc qu'un raccourci pour:
char c[] = {'a', 'b', 'c', '\0'};
Comme tout autre tableau régulier, c
peut être modifié.
Partout ailleurs: il génère un:
Donc, quand vous écrivez:
char *c = "abc";
Ceci est similaire à:
/* __unnamed is magic because modifying it gives UB. */
static char __unnamed[] = "abc";
char *c = __unnamed;
Notez la conversion implicite de char[]
àchar *
, qui est toujours légale.
Ensuite, si vous modifiez c[0]
, vous modifiez également__unnamed
, qui est UB.
Ceci est documenté en 6.4.5 "Littéraux de chaîne":
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 ou plusieurs littéraux de chaîne. 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 suffisant 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 [...]
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.
6.7.8 / 32 "Initialisation" donne un exemple direct:
EXEMPLE 8: La déclaration
char s[] = "abc", t[3] = "abc";
définit les objets de tableau de caractères "simples" s
ett
dont les éléments sont initialisés avec des littéraux de chaîne de caractères.
Cette déclaration est identique à
char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };
Le contenu des tableaux est modifiable. En revanche, la déclaration
char *p = "abc";
définit p
avec le type "pointeur sur char" et l'initialise pour pointer vers un objet de type "tableau de char" de longueur 4 dont les éléments sont initialisés avec une chaîne de caractères littérale. Si une tentative est faite pour p
modifier le contenu du tableau, le comportement n'est pas défini.
Mise en œuvre de GCC 4.8 x86-64 ELF
Programme:
#include <stdio.h>
int main(void) {
char *s = "abc";
printf("%s\n", s);
return 0;
}
Compiler et décompiler:
gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o
La sortie contient:
char *s = "abc";
8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp)
f: 00
c: R_X86_64_32S .rodata
Conclusion: GCC le stocke char*
dans.rodata
section, pas dans.text
.
Si nous faisons de même pour char[]
:
char s[] = "abc";
on obtient:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
donc il est stocké dans la pile (par rapport à %rbp
).
Notez cependant que le script de l'éditeur de liens par défaut place .rodata
et .text
dans le même segment, qui a une autorisation d'exécution mais pas d'écriture. Cela peut être observé avec:
readelf -l a.out
qui contient:
Section to Segment mapping:
Segment Sections...
02 .text .rodata