web-dev-qa-db-fra.com

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

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"?

151
Chris Cooper

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.

120

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.).

50
Jerry Coffin

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 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. 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'utiliser p 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[]: Pile
  • char *s:
    • Section .rodata Du fichier objet
    • le même segment où la section .text du fichier objet est vidée, qui dispose des autorisations de lecture et d'exécution, mais pas de l'écriture

Programme:

#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

  1. [...] Un littéral de chaîne ordinaire est de type "tableau de n const char ”Et durée de stockage statique (3.7)

  2. 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.

22
Justicle

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
14
Alex Budovski

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.

4
Parappa

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"
2
Sahil Jain

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

-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....
0
mihai