Crash lors de la manipulation de char * init’d avec chaîne littérale, mais pas avec malloc

Je lisais un livre sur C aujourd’hui et il était mentionné que ce qui suit était vrai; J’étais tellement curieux de savoir pourquoi j’ai conçu ce programme pour le vérifier; et finalement, postez-le ici pour qu’une personne plus intelligente que moi puisse m’apprendre pourquoi ces deux cas sont différents au moment de l’exécution.

Les spécificités de la question portaient sur la différence au moment de l’exécution entre la manière dont un (char *) est traité, selon qu’il pointe ou non sur une chaîne créée littéralement ou créée avec malloc et une population manuelle.

pourquoi la mémoire allouée par la mémoire est-elle plus protégée de la sorte? De plus, la réponse explique-t-elle le sens du mot “erreur de bus”?

Voici un programme que j’ai écrit et qui demande à l’utilisateur s’il souhaite ou non planter, pour illustrer le fait que le programme comstack bien; et pour souligner que dans ma tête, le code des deux options est conceptuellement identique ; mais c’est pourquoi je suis ici, pour comprendre pourquoi ils ne le sont pas.

// demonstrate the difference between initializing a (char *) // with a literal, vs malloc // and the mutability of the contents thereafter #include  #include  #include  int main() { char cause_crash; char *mySsortingng; printf("Cause crash? "); scanf("%c", &cause_crash); if(cause_crash == 'y') { mySsortingng = "ab"; printf("%s\n", mySsortingng); // ab *mySsortingng = 'x'; // CRASH! printf("%s\n", mySsortingng); } else { mySsortingng = malloc(3 * sizeof(char)); mySsortingng[0] = 'a'; mySsortingng[1] = 'b'; mySsortingng[2] = '\0'; printf("%s\n", mySsortingng); // ab *mySsortingng = 'x'; printf("%s\n", mySsortingng); // xb } return 0; } 

edit: conclusions

Il existe plusieurs bonnes réponses ci-dessous, mais je voudrais résumer ce que j’ai compris de façon succincte ici.

La réponse de base semble être la suivante:

Lorsqu’un compilateur voit qu’un “littéral de chaîne” est assigné à une variable (char *), le pointeur pointe vers la mémoire qui est statique (peut-être en fait une partie du binary, mais généralement appliquée en lecture uniquement par un système de niveau inférieur à votre système). En d’autres termes, la mémoire n’est probablement pas allouée dynamicment à cette partie du programme, mais le pointeur est simplement configuré pour pointer vers une zone de mémoire statique qui contient le contenu de votre littéral.

Il y a quelques choses que je veux appeler à propos de cette résolution:

1. L’ optimisation peut être un motif possible: avec mon compilateur, deux variables différentes (char *) initialisées avec le même littéral de chaîne pointent en fait sur la même adresse:

 char *mySsortingng = "hello"; char *mySecond = "hello"; // the pointers are identical! This is a cool optimization. 

2 Il est intéressant de noter que si la variable est en réalité un tableau de caractères (au lieu d’un (char *)), ceci (# 1) n’est pas vrai. c’était intéressant pour moi parce que j’avais l’impression que les tableaux (post-compilation) étaient identiques aux pointeurs sur les caractères.

 char myArSsortingng[] = "hello"; char myArSecond[] = "hello"; // the pointers are NOT the same 

3 pour résumer ce à quoi plusieurs réponses ont fait allusion: char *mySsortingng = "Hello, World!" n’alloue pas de nouvelle mémoire, il configure simplement mySsortingng pour qu’il pointe vers la mémoire qui existait déjà; peut-être dans le binary, peut-être dans un bloc spécial de mémoire en lecture seule … etc.

4 J’ai trouvé par test que char mySsortingng[] = "Hello, World!" alloue une nouvelle mémoire; Je pense que … ce que je sais, c’est que la chaîne est modifiable lorsqu’elle est créée de cette façon.

Lorsque vous définissez une variable sur un littéral de chaîne, vous la définissez sur une valeur stockée dans la section de données en lecture seule du programme d’assemblage. Ces éléments de données sont constants et toute tentative d’utilisation différente risque de planter.

Lorsque vous utilisez malloc pour récupérer la mémoire, vous obtenez un pointeur permettant de lire / écrire de la mémoire de tas à laquelle vous pouvez faire n’importe quoi.

Ceci est dû à plusieurs raisons. D’une part, le type réel de "Hello, world" est char[13] , ou un pointeur constant sur 13 caractères. Vous ne pouvez pas affecter de valeur à un caractère constant. Mais quand vous faites quelque chose comme ce que vous faites, c’est jeter la constance. Cela signifie que le compilateur ne vous empêchera pas de modifier la mémoire, mais les appels standard C ont un comportement indéfini. Un comportement indéfini peut être n’importe quoi, mais il s’agit généralement d’un crash.

Si vous souhaitez affecter une valeur littérale à char* memory, procédez comme suit:

 char* data = malloc (42); memcpy(data, "Hi!", 4); 

Vous devriez vraiment avoir déclaré mySsortingng tant que const char* . Les littéraux sont stockés dans la mémoire en lecture seule, ils ne peuvent pas être modifiés. Utilisez un caractère char[] si vous devez le modifier.

Quoi

 mySsortingng = "ab"; 

do est assigner l’adresse du littéral chaîne constant qui vit dans la mémoire en lecture seule au pointeur de caractères mySsortingng .

Si vous écrivez dans cette mémoire maintenant, vous obtenez un crash.

OTOH, vous pouvez bien sûr écrire avec plaisir sur la mémoire de malloc() , de sorte que cela fonctionne.

Les normes C spécifient que les chaînes littérales sont statiques et que toute tentative de les modifier entraîne un comportement indéfini. En d’autres termes, ils doivent être considérés en lecture seule .

La mémoire que vous avez atsortingbuée à malloc appartient et vous pouvez la modifier à votre guise.

Les différences réelles peuvent dépendre de l’implémentation, mais généralement chaque type de chaîne est situé dans deux types / zones de mémoire différents:

  • le tas dans le cas de données obtenues avec malloc , et
  • la section de données (en lecture seule) dans le cas de littéraux de chaîne.

Et si vous écriviez ceci:

 &myssortingng = &"ab"; 

Qu’est-ce que cela signifierait pour vous?

Pensez-vous que vous pourriez alors modifier “ab” d’une manière ou d’une autre? Où est & “ab”?

ANS: & “ab” est en mémoire en lecture seule. Lorsque le compilateur voit ce QUOTE, il met cette chaîne en mémoire immuable. Pourquoi? Probablement plus rapide d’une manière ou d’une autre si le moteur d’exécution ne doit pas forcément vérifier les erreurs de segmentation, etc. sur les données de chaîne qui ne devraient vraiment jamais changer.