Le code suivant reçoit une erreur de segmentation sur la ligne 2:
char *str = "string";
str[0] = 'z'; // could be also written as *str = 'z'
printf("%s\n", str);
Bien que cela fonctionne parfaitement bien:
char str[] = "string";
str[0] = 'z';
printf("%s\n", str);
Testé avec MSVC et GCC.
c
segmentation-fault
c-strings
Markus
la source
la source
Réponses:
Voir la FAQ C, question 1.32
la source
mprotect
pour onduler la protection en lecture seule (voir ici ).Normalement, les littéraux de chaîne sont stockés en mémoire morte lorsque le programme est exécuté. Cela vous empêche de modifier accidentellement une constante de chaîne. Dans votre premier exemple,
"string"
est stocké en mémoire morte et*str
pointe vers le premier caractère. Le défaut de segmentation se produit lorsque vous essayez de remplacer le premier caractère par'z'
.Dans le deuxième exemple, la chaîne
"string"
est copiée par le compilateur de son répertoire d'origine en lecture seule vers lestr[]
tableau. La modification du premier caractère est alors autorisée. Vous pouvez le vérifier en imprimant l'adresse de chacun:De plus, l'impression de la taille de
str
dans le deuxième exemple vous montrera que le compilateur lui a alloué 7 octets:la source
%zu
pour imprimersize_t
La plupart de ces réponses sont correctes, mais juste pour ajouter un peu plus de clarté ...
La "mémoire morte" à laquelle les gens font référence est le segment de texte en termes ASM. C'est le même endroit en mémoire où les instructions sont chargées. Ceci est en lecture seule pour des raisons évidentes comme la sécurité. Lorsque vous créez un char * initialisé à une chaîne, les données de la chaîne sont compilées dans le segment de texte et le programme initialise le pointeur pour pointer dans le segment de texte. Donc, si vous essayez de le changer, kaboom. Segfault.
Lorsqu'il est écrit sous forme de tableau, le compilateur place à la place les données de chaîne initialisées dans le segment de données, qui est le même endroit que vos variables globales et autres. Cette mémoire est modifiable, car il n'y a pas d'instructions dans le segment de données. Cette fois, lorsque le compilateur initialise le tableau de caractères (qui n'est encore qu'un caractère *), il pointe vers le segment de données plutôt que vers le segment de texte, que vous pouvez modifier en toute sécurité au moment de l'exécution.
la source
C99 N1256 draft
Il existe deux utilisations différentes des littéraux de chaîne de caractères:
Initialiser
char[]
:C'est "plus magique", et décrit au 6.7.8 / 14 "Initialisation":
Ce n'est donc qu'un raccourci pour:
Comme tout autre tableau régulier,
c
peut être modifié.Partout ailleurs: il génère un:
Donc, quand vous écrivez:
Ceci est similaire à:
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":
6.7.8 / 32 "Initialisation" donne un exemple direct:
Mise en œuvre de GCC 4.8 x86-64 ELF
Programme:
Compiler et décompiler:
La sortie contient:
Conclusion: GCC le stocke
char*
dans.rodata
section, pas dans.text
.Si nous faisons de même pour
char[]
:on obtient:
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:qui contient:
la source
Dans le premier code, "chaîne" est une constante de chaîne et les constantes de chaîne ne doivent jamais être modifiées car elles sont souvent placées en mémoire morte. "str" est un pointeur utilisé pour modifier la constante.
Dans le deuxième code, "chaîne" est un initialiseur de tableau, sorte de raccourci pour
"str" est un tableau alloué sur la pile et peut être modifié librement.
la source
str
est global oustatic
.Parce que le type de
"whatever"
dans le contexte du 1er exemple estconst char *
(même si vous l'assignez à un caractère non const *), ce qui signifie que vous ne devriez pas essayer d'écrire dessus.Le compilateur a appliqué cela en plaçant la chaîne dans une partie en lecture seule de la mémoire, d'où l'écriture génère une erreur de segmentation.
la source
Pour comprendre cette erreur ou ce problème, vous devez d'abord connaître la différence b / w le pointeur et le tableau alors voici d'abord je vous explique les différences b / w les
tableau de chaînes
Dans le tableau mémoire est stocké dans les cellules de mémoire continue, stockée sous forme
[h][e][l][l][o][\0] =>[]
est une cellule de mémoire char taille d'octets, et cette cellules de mémoire continue peut être accès par nom de nom strArray here.so ici tableau de chaînesstrarray
contenant lui - même tous les caractères de chaîne initialisée à it.In cette cas ici"hello"
afin que nous puissions facilement changer son contenu en mémoire en accédant à chaque caractère par sa valeur d'indexet sa valeur a changé en valeur
'm'
si strarray changé en"mello"
;un point à noter ici que nous pouvons changer le contenu du tableau de chaînes en changeant caractère par caractère mais ne pouvons pas initialiser directement une autre chaîne comme
strarray="new string"
n'est pas valideAiguille
Comme nous le savons tous, le pointeur pointe vers l'emplacement de la mémoire en mémoire, le pointeur non initialisé pointe vers un emplacement de mémoire aléatoire, et après l'initialisation pointe vers un emplacement de mémoire particulier
ici le pointeur ptr est initialisé en chaîne
"hello"
qui est une chaîne constante stockée dans la mémoire morte (ROM) afin"hello"
ne peut pas être modifiée car elle est stockée dans la ROMet ptr est stocké dans la section pile et pointant vers une chaîne constante
"hello"
donc ptr [0] = 'm' n'est pas valide car vous ne pouvez pas accéder à la mémoire en lecture seule
Mais ptr peut être initialisé directement à une autre valeur de chaîne car il ne s'agit que d'un pointeur afin qu'il puisse pointer vers n'importe quelle adresse mémoire de variable de son type de données
la source
Les ensembles ci-dessus
str
pointent vers la valeur littérale"string"
qui est codée en dur dans l'image binaire du programme, qui est probablement marquée en lecture seule en mémoire.Tente donc
str[0]=
d'écrire dans le code en lecture seule de l'application. Je suppose que cela dépend probablement du compilateur.la source
alloue un pointeur sur un littéral de chaîne, que le compilateur place dans une partie non modifiable de votre exécutable;
alloue et initialise un tableau local qui est modifiable
la source
int *b = {1,2,3)
comme nous écrivonschar *s = "HelloWorld"
?La FAQ C à laquelle @matli a lié le mentionne, mais personne d'autre ici ne l'a encore fait, donc pour clarification: si un littéral de chaîne (chaîne entre guillemets doubles dans votre source) est utilisé ailleurs que pour initialiser un tableau de caractères (c'est-à-dire: @ Deuxième exemple de Mark, qui fonctionne correctement), cette chaîne est stockée par le compilateur dans une table de chaînes statique spéciale , ce qui s'apparente à la création d'une variable statique globale (en lecture seule, bien sûr) qui est essentiellement anonyme (n'a pas de variable "nom "). La partie en lecture seule est la partie importante, et c'est pourquoi le premier exemple de code de @ Mark segfaults.
la source
int *b = {1,2,3)
comme nous écrivonschar *s = "HelloWorld"
?le
ligne définit un pointeur et le pointe vers une chaîne littérale. La chaîne littérale n'est pas accessible en écriture donc quand vous faites:
vous obtenez une faute de segmentation. Sur certaines plates-formes, le littéral peut être en mémoire inscriptible, vous ne verrez donc pas de défaut de segmentation, mais c'est un code non valide (entraînant un comportement indéfini) malgré tout.
La ligne:
alloue un tableau de caractères et copie la chaîne littérale dans ce tableau, qui est entièrement accessible en écriture, donc la mise à jour suivante ne pose aucun problème.
la source
int *b = {1,2,3)
comme nous écrivonschar *s = "HelloWorld"
?Les littéraux de chaîne tels que "chaîne" sont probablement alloués dans l'espace d'adressage de votre exécutable en tant que données en lecture seule (donnez ou prenez votre compilateur). Lorsque vous allez le toucher, cela vous fait paniquer que vous êtes dans sa zone de maillot de bain et vous le signale avec une faute de seg.
Dans votre premier exemple, vous obtenez un pointeur vers ces données const. Dans votre deuxième exemple, vous initialisez un tableau de 7 caractères avec une copie des données const.
la source
la source
En premier lieu,
str
est un pointeur qui pointe vers"string"
. Le compilateur est autorisé à placer des littéraux de chaîne dans des emplacements en mémoire où vous ne pouvez pas écrire, mais seulement lire. (Cela aurait dû déclencher un avertissement, car vous affectez unconst char *
à unchar *
. Avez-vous désactivé les avertissements ou les avez-vous simplement ignorés?)En second lieu, vous créez un tableau, c'est-à-dire la mémoire à laquelle vous avez un accès complet, et l'initialisez avec
"string"
. Vous créez unchar[7]
(six pour les lettres, un pour la terminaison '\ 0'), et vous faites ce que vous voulez avec.la source
const
préfixechar [N]
, nonconst char [N]
, donc il n'y a pas d'avertissement. (Vous pouvez changer cela dans gcc au moins en passant-Wwrite-strings
.)Supposons que les chaînes soient,
Dans le premier cas, le littéral doit être copié lorsque «a» entre dans le champ d'application. Ici, «a» est un tableau défini sur la pile. Cela signifie que la chaîne sera créée sur la pile et que ses données sont copiées à partir de la mémoire de code (texte), qui est généralement en lecture seule (ceci est spécifique à l'implémentation, un compilateur peut également placer ces données de programme en lecture seule dans une mémoire accessible en écriture. ).
Dans le second cas, p est un pointeur défini sur la pile (portée locale) et faisant référence à un littéral de chaîne (données de programme ou texte) stocké ailleurs. Modifier une telle mémoire n'est généralement pas une bonne pratique ni encouragé.
la source
La première est une chaîne constante qui ne peut pas être modifiée. Le second est un tableau avec une valeur initialisée, il peut donc être modifié.
la source
Une erreur de segmentation est causée lorsque vous essayez d'accéder à la mémoire qui est inaccessible.
char *str
est un pointeur sur une chaîne non modifiable (la raison pour laquelle segfault).alors que
char str[]
est un tableau et peut être modifiable ..la source