Pourquoi une erreur de segmentation se produit-elle lorsque j'écris dans une chaîne initialisée avec & # 8220; char * s & # 8221; mais pas les caractères [] & # 8221 ;?

StackOverflow https://stackoverflow.com/questions/164194

  •  03-07-2019
  •  | 
  •  

Question

Le code suivant reçoit l'erreur seg 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.

Était-ce utile?

La solution

Voir la FAQ C, Question 1.32

  

Q : quelle est la différence entre ces initialisations?
   char a [] = "chaîne de caractères" " ;;;
   char * p = "chaîne littérale" " ;;
  Mon programme se bloque si j'essaie d'attribuer une nouvelle valeur à p [i] .

     

A : un littéral de chaîne (terme formel   pour une chaîne entre guillemets doubles en C   source) peut être utilisé en deux légèrement   différentes manières:

     
      
  1. En tant qu'initialiseur pour un tableau de caractères, comme dans la déclaration de caractère a [] , il spécifie les valeurs initiales   des caractères de ce tableau (et,   si nécessaire, sa taille).
  2.   
  3. Partout ailleurs, il se transforme en un tableau statique de noms non nommé   et ce tableau sans nom peut être stocké   en mémoire en lecture seule, et qui   donc ne peut pas nécessairement être   modifié. Dans un contexte d'expression,   le tableau est converti à la fois en un   pointeur, comme d’habitude (voir section 6),   la seconde déclaration initialise p   pointer vers le premier du tableau sans nom   élément.
  4.   
     

Certains compilateurs ont un commutateur   contrôler si les littéraux de chaîne   sont inscriptibles ou non (pour compiler des anciens   code), et certains peuvent avoir des options pour   provoquer formellement les littéraux de chaîne   traités comme des tableaux de const char (pour   meilleure erreur de capture).

Autres conseils

Normalement, les littéraux de chaîne sont stockés dans la mémoire en lecture seule lors de l'exécution du programme. Cela vous évitera de changer accidentellement une constante de chaîne. Dans votre premier exemple, "chaîne" est stocké dans une mémoire en lecture seule et * str pointe sur le premier caractère. La erreur de segmentation se produit lorsque vous essayez de remplacer le premier caractère par 'z' .

Dans le deuxième exemple, la chaîne "chaîne" est copiée par le compilateur depuis son emplacement en lecture seule vers le str [] tableau. Ensuite, le changement du premier caractère est autorisé. Vous pouvez vérifier cela en imprimant l'adresse de chacun:

printf("%p", str);

De même, l’impression de la taille de str dans le deuxième exemple vous montrera que le compilateur a alloué 7 octets pour cela:

printf("%d", sizeof(str));

La plupart de ces réponses sont correctes, mais pour ajouter un peu plus de clarté ...

La "mémoire en lecture seule" que les gens se réfèrent à 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 telles que la sécurité. Lorsque vous créez un caractère * 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 afin qu'il pointe 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 les données de chaîne initialisées dans le segment de données, ce qui correspond au même emplacement 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 dans le segment de données plutôt que dans le segment de texte, que vous pouvez modifier en toute sécurité au moment de l'exécution.

  

Pourquoi une erreur de segmentation est-elle générée lors de l'écriture d'une chaîne?

Projet de C99 N1256

Il existe deux utilisations différentes des littéraux de chaîne de caractères:

  1. Initialiser char [] :

    char c[] = "abc";      
    

    Ceci est "plus magique" et décrit à 6.7.8 / 14 "Initialisation":

      

    Un tableau de type caractère peut être initialisé par un littéral de chaîne de caractères, éventuellement   enfermés dans des accolades. Caractères successifs de la chaîne de caractères (y compris le   terminer le caractère nul s'il y a de la place ou si le tableau est de taille inconnue) initialiser le   éléments du tableau.

    Il ne s'agit donc que d'un raccourci pour:

    char c[] = {'a', 'b', 'c', '\0'};
    

    Comme tout autre tableau normal, c peut être modifié.

  2. 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 [] en char * , ce qui est toujours légal.

    Ensuite, si vous modifiez c [0] , vous modifiez également __ sans nom , qui correspond à UB.

    Ceci est documenté à la section 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 multi-octet.   séquence de caractères résultant d'un littéral de chaîne ou de littéraux. Le caractère multi-octets   la séquence est ensuite utilisée pour initialiser un tableau de durée et de durée 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   tapez char et sont initialisés avec les octets individuels du caractère multi-octets   séquence [...]

         

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

6.7.8 / 32 "Initialisation" donne un exemple direct:

  

EXEMPLE 8: La déclaration

char s[] = "abc", t[3] = "abc";
     

définit " plain " objets de tableau de caractères s et t 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. Par contre, la déclaration

char *p = "abc";
     

définit p avec le type " pointeur sur caractère " et l'initialise pour qu'il pointe vers un objet de type "tableau de caractères". de longueur 4 dont les éléments sont initialisés avec un littéral de chaîne de caractères. Si vous essayez d'utiliser p pour modifier le contenu du tableau, le comportement n'est pas défini.

Mise en œuvre du facteur de résolution des litiges GCC 4.8 x86-64

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   
 char s[] = "abc";
x0,-0x8(%rbp) f: 00 c: R_X86_64_32S .rodata

Conclusion: GCC stocke char * dans la section .rodata et non dans .text .

Si nous faisons la même chose pour char [] :

17:   c7 45 f0 61 62 63 00    movl   
readelf -l a.out
x636261,-0x10(%rbp)

on obtient:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

afin qu'il soit 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 dispose de l'autorisation d'exécution mais pas de l'autorisation d'écriture. Ceci peut être observé avec:

<*>

qui contient:

<*>

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 dans la mémoire en lecture seule. & str; str " est un pointeur utilisé pour modifier la constante.

Dans le deuxième code, "chaîne". est un initialiseur de tableau, sorte de main courte pour

char str[7] =  { 's', 't', 'r', 'i', 'n', 'g', '\0' };

& str; str " est un tableau alloué sur la pile et peut être modifié librement.

Parce que le type de "quel que soit" dans le contexte du premier exemple est const char * (même si vous l'assignez à un char non-const *) , ce qui signifie que vous ne devriez pas essayer d’écrire.

Le compilateur a imposé cela en plaçant la chaîne dans une partie de la mémoire en lecture seule. Son écriture génère donc un segfault.

Pour comprendre cette erreur ou ce problème, vous devez d’abord connaître la différence entre le pointeur et le tableau.   alors voici d'abord je vous expliquer les différences b / w les

tableau de chaînes

 char strarray[] = "hello";

Dans la mémoire, le tableau est stocké dans des cellules de mémoire continues, sous la forme [h] [e] [l] [l] [o] [\ 0] = > [] est égal à 1 caractère sur octet. cellule mémoire de taille, et ces cellules de mémoire continue peuvent être accédées par le nom nommé strarray ici.ici ici string array strarray contenant lui-même tous les caractères de la chaîne initialisée à elle.dans ce cas ici ; afin que nous puissions facilement changer le contenu de sa mémoire en accédant à chaque caractère par sa valeur d'index

`strarray[0]='m'` it access character at index 0 which is 'h'in strarray

et sa valeur changée en 'm' de sorte que la valeur de strarray soit changée en "quot". ;

Il convient de noter ici que nous pouvons modifier le contenu du tableau de chaînes en modifiant caractère par caractère mais ne pouvons pas initialiser directement une autre chaîne comme strarray = "nouvelle chaîne" n'est pas valide

Pointeur

Comme nous le savons tous, les pointeurs pointent vers un emplacement mémoire en mémoire, le pointeur non initialisé pointe vers l'emplacement de la mémoire aléatoire de sorte que, après l'initialisation, il pointe vers l'emplacement de la mémoire en question

char *ptr = "hello";

ici le pointeur ptr est initialisé sur la chaîne "hello" qui est une chaîne constante stockée dans la mémoire morte (ROM), de sorte que "hello" ne peut pas être modifié car il est stocké dans la ROM

et ptr sont stockés dans la section de pile et pointent vers la chaîne constante "bonjour"

so 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 puisqu'il s'agit simplement d'un pointeur afin qu'il puisse être pointé sur n'importe quelle adresse mémoire de la variable de son type de données

ptr="new string"; is valid
char *str = "string";  

Ce qui précède fait en sorte que str pointe sur la valeur littérale "chaîne" qui est codée en dur dans l'image binaire du programme, qui est probablement marquée en lecture seule. en mémoire.

Donc str [0] = tente d'écrire dans le code en lecture seule de l'application. Je suppose que cela dépend probablement du compilateur.

char *str = "string";

alloue un pointeur sur un littéral de chaîne que le compilateur insère dans une partie non modifiable de votre exécutable;

char str[] = "string";

alloue et initialise un tableau local modifiable

La FAQ C à laquelle @matli est liée le mentionne, mais personne d'autre ici ne l'a encore fait, donc pour plus de précision: si un littéral de chaîne (chaîne entre guillemets doubles dans votre source) est utilisé partout ailleurs que pour initialiser un tableau de caractères (c'est-à-dire: le 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 revient à créer 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 la plus importante. C'est pourquoi le premier exemple de code de la marque @ Mark segfaults.

Le

 char *str = "string";
La 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, alors quand vous le ferez:

  str[0] = 'z';

vous obtenez une faute seg. Sur certaines plates-formes, le littéral peut se trouver dans la mémoire inscriptible. Vous ne verrez donc pas de erreur de segmentation, mais son code n'est pas valide (ce qui entraîne un comportement indéfini).

La ligne:

char str[] = "string";

alloue un tableau de caractères et copie dans ce tableau, qui est entièrement accessible en écriture, de sorte que la mise à jour ultérieure ne pose aucun problème.

Les littéraux de chaîne tels que "chaîne". sont probablement alloués dans l'espace d'adressage de votre exécutable sous forme de données en lecture seule (donnez ou prenez votre compilateur). Lorsque vous le touchez, il est paniqué de savoir que vous êtes dans sa zone de maillot de bain et vous laisse savoir avec une faute distincte.

Dans votre premier exemple, vous obtenez un pointeur sur ces données const. Dans votre deuxième exemple, vous initialisez un tableau de 7 caractères avec une copie des données const.

// create a string constant like this - will be read only
char *str_p;
str_p = "String constant";

// create an array of characters like this 
char *arr_p;
char arr[] = "String in an array";
arr_p = &arr[0];

// now we try to change a character in the array first, this will work
*arr_p = 'E';

// lets try to change the first character of the string contant
*str_p = 'G'; // this will result in a segmentation fault. Comment it out to work.


/*-----------------------------------------------------------------------------
 *  String constants can't be modified. A segmentation fault is the result,
 *  because most operating systems will not allow a write
 *  operation on read only memory.
 *-----------------------------------------------------------------------------*/

//print both strings to see if they have changed
printf("%s\n", str_p); //print the string without a variable
printf("%s\n", arr_p); //print the string, which is in an array. 

En premier lieu, str est un pointeur qui pointe sur " chaîne " . Le compilateur est autorisé à placer des littéraux de chaîne dans des emplacements en mémoire dans lesquels vous ne pouvez pas écrire, mais vous ne pouvez que lire. (Cela aurait vraiment dû déclencher un avertissement, puisque vous affectez un const char * à un char * . Avez-vous désactivé les avertissements ou les avez-vous simplement ignorés? )

Ensuite, vous créez un tableau, qui correspond à la mémoire à laquelle vous avez un accès complet, et vous l'initialisez avec " chaîne . Vous créez un char [7] (six pour les lettres, un pour le '\ 0' final) et vous en faites ce que vous voulez.

Supposons que les chaînes sont,

char a[] = "string literal copied to stack";
char *p  = "string literal referenced by p";

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 seront copiées à partir de la mémoire de code (texte), qui est généralement en lecture seule (ceci est spécifique à la mise en oeuvre, un compilateur peut placer ces données de programme en lecture seule dans une mémoire en lecture seule). ).

Dans le second cas, p est un pointeur défini sur une pile (portée locale) et faisant référence à un littéral de chaîne (données de programme ou texte) stocké ailleurs. La modification d'une telle mémoire n'est généralement pas une bonne pratique, ni encouragée.

La première est une chaîne constante qui ne peut pas être modifiée. Deuxièmement, un tableau avec une valeur initialisée, il peut donc être modifié.

Une erreur de segmentation est provoquée lorsque vous voulez accéder à la mémoire qui n’est pas accessible.

char * str est un pointeur sur une chaîne non modifiable (raison de l'obtention de l'erreur de segmentation) ..

alors que char str [] est un tableau et peut être modifiable.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top