Je suis intéressé à savoir où les littéraux de chaîne sont alloués/stockés.
J'ai trouvé une réponse intrigante ici , en disant:
Définir une chaîne en ligne incorpore réellement les données dans le programme lui-même et ne peut pas être modifié (certains compilateurs le permettent par une astuce intelligente, ne vous embêtez pas).
Mais, cela avait à voir avec C++, sans mentionner que cela dit de ne pas déranger.
Je dérange. = D
Ma question est donc de savoir où et comment mon littéral de chaîne est conservé. 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 souhaite élaborer sur le "tour intelligent"?
Une technique courante consiste à placer les littéraux de chaîne dans la section "données en lecture seule" qui est mappée en lecture seule dans l'espace processus (raison pour laquelle vous ne pouvez pas le modifier).
Cela varie selon la plate-forme. Par exemple, les architectures de puce plus simples peuvent ne pas prendre en charge les segments de mémoire en lecture seule, ce qui permet d'écrire dans le segment de données.
Essayez plutôt de trouver une astuce pour rendre les littéraux de chaîne 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 le modifier.
Il n'y a pas une réponse à cela. Les normes C et C++ indiquent simplement que les littéraux de chaîne ont une durée de stockage statique, 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 pour lequel vous écrivez et les capacités du format de fichier exécutable utilisé, ceux-ci peuvent être stockés avec le code du programme dans le segment de texte ou peuvent comporter un segment distinct pour les données initialisées.
La détermination des détails variera également selon la plate-forme - la plupart du temps, il est probable que des outils vous permettent de savoir où cela se trouve. Certains vous donneront même le contrôle de tels détails, si vous le souhaitez (par exemple, gnu ld vous permet de fournir un script expliquant comment regrouper des données, du code, etc.).
Pourquoi ne devrais-je pas essayer de le modifier?
Parce que c'est un comportement indéfini. Citation de brouillon C99 N1256 6.7.8/32 "Initialisation" :
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. D'autre part, la déclaration
char *p = "abc";
définit
p
avec le type "pointeur sur caractère" et l'initialise pour pointer sur un objet de type "tableau de caractère" de longueur 4 dont les éléments sont initialisés avec un littéral de chaîne de caractères. Si vous tentez d'utiliserp
pour modifier le contenu du tableau, le comportement n'est pas défini.
Où vont-ils?
GCC 4.8 x86-64 ELF Ubuntu 14.04:
char s[]
: Pilechar *s
: .rodata
Du fichier objet.text
du fichier objet est vidée, qui dispose des autorisations de lecture et d'exécution, mais pas de l'écritureProgramme:
#include <stdio.h>
int main() {
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
La chaîne est donc stockée dans la section .rodata
.
Ensuite:
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 R E 200000
Section to Segment mapping:
Segment Sections...
02 .text .rodata
Cela signifie que le script de l'éditeur de liens par défaut vide à la fois .text
Et .rodata
Dans un segment qui peut être exécuté mais non modifié (Flags = R E
). 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 pile (par rapport à %rbp
), et nous pouvons bien sûr le modifier.
FYI, en sauvegardant les autres réponses:
La norme: ISO/IEC 14882: 20 dit:
2.13. Littéraux de chaîne
[...] Un littéral de chaîne ordinaire est de type "tableau de
n const char
”Et durée de stockage statique (3.7)Que tous les littéraux de chaîne soient distincts (c'est-à-dire stockés dans des objets non chevauchants) est défini par l'implémentation. L'effet de tenter de modifier un littéral de chaîne n'est pas défini.
gcc fait un .rodata
section qui est mappée "quelque part" dans l'espace adresse et est marquée en lecture seule,
Visual C++ (cl.exe
) fait un .rdata
section 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 programmé en Assemblée, vous pourriez placer des littéraux de chaîne dans le segment de données de votre programme Assembly. Votre compilateur C fait quelque chose comme ça, mais tout dépend du système pour lequel vous compilez le binaire.
Les littéraux de chaîne sont fréquemment alloués à la mémoire en lecture seule, ce qui les rend immuables. Cependant, dans certains compilateurs, la modification est possible par un "astuce intelligente" .. Et l'astuce intelligente consiste à "utiliser un pointeur de caractère pointant vers la mémoire" ..
char *tabHeader = "Sound";
*tabHeader = 'L';
printf("%s\n",tabHeader); // Displays "Lound"
Comme cela peut différer d'un compilateur à l'autre, la meilleure méthode consiste à filtrer un vidage d'objet pour le littéral recherché:
objdump -s main.o | grep -B 1 str
où -s
force objdump
à afficher le contenu complet de toutes les sections, main.o
est le fichier objet, -B 1
force grep
à imprimer également une ligne avant la correspondance (afin que vous puissiez voir le nom de la section) et str
est le littéral de chaîne 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....