Littéraux de chaîne: Où vont-ils?

Je m’intéresse à l’endroit où les littéraux de chaîne sont atsortingbués / stockés.

J’ai trouvé une réponse intéressante ici :

Définir une chaîne en ligne incorpore en fait les données dans le programme lui-même et ne peut pas être modifié (certains compilateurs le permettent par un truc intelligent, ne vous embêtez pas).

Mais cela concernait le C ++, sans parler du fait que cela ne gênait pas.

Je dérange. = D

Donc, ma question est où et comment ma chaîne littérale est-elle conservée? Pourquoi ne devrais-je pas essayer de le modifier? La mise en œuvre varie-t-elle selon la plate-forme? Est-ce que quelqu’un a envie d’élaborer sur le “truc intelligent”?

Une technique courante consiste à placer les littéraux de chaîne dans la section “lecture seule-données” qui est mappée dans l’espace de processus en lecture seule (c’est pourquoi vous ne pouvez pas la modifier).

Cela varie selon la plate-forme. Par exemple, des architectures de puces plus simples peuvent ne pas prendre en charge les segments de mémoire en lecture seule afin que le segment de données soit accessible en écriture.

Au lieu de cela, essayez de trouver un truc pour rendre les chaînes de caractères modifiables (cela dépendra beaucoup de votre plate-forme et pourrait changer avec le temps), utilisez simplement des tableaux:

char foo[] = "..."; 

Le compilateur organisera l’initialisation du tableau à partir du littéral et vous pourrez modifier le tableau.

Il n’y a pas de réponse à cela. Les normes C et C ++ indiquent simplement que les littéraux de chaîne ont une durée de stockage statique, que toute tentative de les modifier donne un comportement indéfini et que plusieurs littéraux de chaîne ayant le même contenu peuvent ou non partager le même stockage.

Selon le système que vous écrivez et les capacités du format de fichier exécutable utilisé, ils peuvent être stockés avec le code du programme dans le segment de texte, ou ils peuvent avoir un segment distinct pour les données initialisées.

La détermination des détails variera également en fonction de la plate-forme – probablement des outils pouvant vous indiquer où ils se trouvent. Certains vous donneront même le contrôle de ces détails, si vous le souhaitez (par exemple, gnu ld vous permet de fournir un script pour tout savoir sur le regroupement des données, du code, etc.)

Pourquoi ne devrais-je pas essayer de le modifier?

Parce que c’est un comportement indéfini. Citation du projet C99 N1256 6.7.8 / 32 “Initialisation” :

EXEMPLE 8: La déclaration

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

définit les objects de tableau “plain” 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 “pointer to char” et l’initialise pour pointer vers un object de type “array of char” de longueur 4 dont les éléments sont initialisés avec une chaîne de caractères littérale. Si vous tentez d’utiliser p pour modifier le contenu du tableau, le comportement est indéfini.

Où vont-ils?

GCC 4.8 x86-64 ELF Ubuntu 14.04:

  • char s[] : stack
  • char *s :
    • Section .rodata du fichier object
    • le même segment où la section .text du fichier object est sauvegardée, avec des permissions de lecture et d’exécution, mais pas d’écriture

Programme:

 #include  int main() { char *s = "abc"; printf("%s\n", s); return 0; } 

Comstackr et décomstackr:

 gcc -ggdb -std=c99 -c main.c objdump -Sr main.o 

Le résultat contient:

  char *s = "abc"; 8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp) f: 00 c: R_X86_64_32S .rodata 

La chaîne est donc stockée dans la section .rodata .

Alors:

 readelf -l a.out 

Contient (simplifié):

 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x0000000000000704 0x0000000000000704 RE 200000 Section to Segment mapping: Segment Sections... 02 .text .rodata 

Cela signifie que le script de l’éditeur de liens par défaut .rodata à la fois .text et .rodata dans un segment pouvant être exécuté mais non modifié ( Flags = RE ). Tenter de modifier un tel segment entraîne une erreur de segmentation sous Linux.

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

  char s[] = "abc"; 

on obtient:

 17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp) 

il est donc stocké dans la stack (par rapport à %rbp ), et nous pouvons bien sûr le modifier.

FYI, sauvegardez simplement les autres réponses:

La norme: ISO / IEC 14882: 2003 dit:

2.13. Littéraux de chaîne

  1. […] Un littéral de chaîne ordinaire a le type «tableau de n const char » et la durée de stockage statique (3.7)

  2. Que tous les littéraux de chaîne soient distincts (c’est-à-dire qu’ils soient stockés dans des objects ne se chevauchant pas) est défini par l’implémentation. L’effet de la tentative de modification d’un littéral de chaîne n’est pas défini.

gcc crée une section .rodata qui est mappée “quelque part” dans l’espace d’adressage et qui est marquée en lecture seule,

Visual C ++ ( cl.exe ) crée une section .rdata dans le même but.

Vous pouvez regarder la sortie de dumpbin ou objdump (sous Linux) pour voir les sections de votre exécutable.

Par exemple

 >dumpbin vec1.exe Microsoft (R) COFF/PE Dumper Version 8.00.50727.762 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file vec1.exe File Type: EXECUTABLE IMAGE Summary 4000 .data 5000 .rdata <-- here are strings and other read-only stuff. 14000 .text 

Cela dépend du format de votre exécutable . Une façon de penser à cela est que si vous étiez en programmation d’assemblage, vous pourriez mettre des chaînes littérales dans le segment de données de votre programme d’assemblage. Votre compilateur C fait quelque chose comme ça, mais tout dépend de quel système vous êtes en train de comstackr.

Les littéraux de chaîne sont fréquemment atsortingbués à la mémoire en lecture seule, ce qui les rend immuables. Cependant, dans certains compilateurs, la modification est possible par un “truc intelligent”. Et le truc intelligent consiste à “utiliser un pointeur de caractère pointant vers la mémoire” .. souvenez-vous de certains compilateurs, peut-être ne le permet pas …

 char *tabHeader = "Sound"; *tabHeader = 'L'; printf("%s\n",tabHeader); // Displays "Lound" 

Comme cela peut différer d’un compilateur à l’autre, le meilleur moyen est de filtrer un vidage d’object pour le littéral de chaîne recherché:

 objdump -s main.o | grep -B 1 str 

-s force objdump à afficher le contenu complet de toutes les sections, main.o est le fichier object, -B 1 force grep à imprimer également une ligne avant la correspondance (pour que vous puissiez voir le nom de la section) et str est la chaîne littéral que vous recherchez.

Avec gcc sur une machine Windows et une variable déclarée dans main comme

 char *c = "whatever"; 

fonctionnement

 objdump -s main.o | grep -B 1 whatever 

résultats

 Contents of section .rdata: 0000 77686174 65766572 00000000 whatever....