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

162

Je suis intéressé par l'endroit où les littéraux de chaîne sont alloués / stockés.

J'ai trouvé une réponse intéressante ici , en disant:

La définition d'une chaîne en ligne intègre en fait les données dans le programme lui-même et ne peut pas être modifiée (certains compilateurs le permettent par une astuce intelligente, ne vous inquiétez pas).

Mais, cela avait à voir avec C ++, sans oublier que cela dit de ne pas déranger.

Je dérange. = D

Ma question est donc 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? Quelqu'un souhaite-t-il élaborer sur le «truc intelligent»?

Chris Cooper
la source

Réponses:

126

Une technique courante consiste à placer les littéraux de chaîne dans la section "données en lecture seule" qui est mappée dans l'espace de processus en lecture seule (c'est pourquoi vous ne pouvez pas le 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, de sorte que le segment de données sera inscriptible.

Essayez plutôt de trouver une astuce pour rendre les littéraux de chaîne modifiables (cela dépendra fortement de votre plate-forme et pourrait changer avec le temps), utilisez simplement des tableaux:

char foo[] = "...";

Le compilateur fera en sorte que le tableau soit initialisé à partir du littéral et vous pouvez modifier le tableau.

R Samuel Klatchko
la source
5
Oui, j'utilise des tableaux lorsque je veux avoir des chaînes mutables. J'étais juste curieux. Merci.
Chris Cooper
2
Cependant, vous devez faire attention au dépassement de la mémoire tampon lorsque vous utilisez des tableaux pour des chaînes mutables - le simple fait d'écrire une chaîne plus longue que la longueur du tableau (par exemple foo = "hello"dans ce cas) peut provoquer des effets secondaires involontaires ... (en supposant que vous ne allouer de la mémoire avec newou quelque chose)
johnny
2
Est-ce que lors de l'utilisation de la chaîne de tableau va dans la pile ou ailleurs?
Suraj Jain du
Ne pouvons-nous pas utiliser char *p = "abc";pour créer des chaînes mutables comme le dit différemment @ChrisCooper
KPMG
52

Il n'y a pas de réponse unique à cela. Les normes C et C ++ disent 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 plusieurs littéraux de chaîne avec le même contenu peuvent partager ou non le même stockage.

Selon le système pour lequel vous écrivez et les capacités du format de fichier exécutable qu'il utilise, ils peuvent être stockés avec le code de 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 - incluez très probablement des outils qui peuvent vous dire où cela se trouve. Certains vous donneront même le contrôle sur des détails comme ça, si vous le souhaitez (par exemple, gnu ld vous permet de fournir un script pour tout dire sur la façon de regrouper les données, le code, etc.)

Jerry Coffin
la source
1
Je trouve peu probable que les données de chaîne soient stockées directement dans le segment .text. Pour les littéraux vraiment courts, je pourrais voir le compilateur générer du code tel que movb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp)pour la chaîne "AB", mais la grande majorité du temps, ce sera dans un segment non-code tel que .dataou .rodataou similaire (selon que la cible prend en charge ou non segments en lecture seule).
Adam Rosenfield
Si les littéraux de chaîne sont valides pendant toute la durée du programme, même pendant la destruction d'objets statiques, est-il valide de renvoyer une référence const à un littéral de chaîne? Pourquoi ce programme affiche une erreur d'exécution voir ideone.com/FTs1Ig
Destructor
@AdamRosenfield: Si vous vous ennuyez parfois, vous voudrez peut-être regarder (par exemple) l'ancien format UNIX a.out (par exemple, freebsd.org/cgi/… ). Une chose que vous devriez rapidement remarquer est qu'il ne prend en charge qu'un seul segment de données, qui est toujours accessible en écriture. Donc, si vous voulez des littéraux de chaîne en lecture seule, le seul endroit où ils peuvent aller est le segment de texte (et oui, à l'époque, les éditeurs de liens faisaient souvent exactement cela).
Jerry Coffin
48

Pourquoi ne devrais-je pas essayer de le modifier?

Parce que c'est un comportement indéfini. Citation de C99 N1256 draft 6.7.8 / 32 "Initialisation" :

EXEMPLE 8: La déclaration

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

définit des objets de tableau de caractères "simples" set tdont les éléments sont initialisés avec des chaînes de caractères littérales.

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 pavec le type "pointer to char" et l'initialise pour pointer vers un objet 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 une tentative d'utilisation est faite pour pmodifier 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:
    • .rodata section du fichier objet
    • le même segment où la .textsection du fichier objet est vidée, qui a des autorisations de lecture et d'exécution, mais pas d'écriture

Programme:

#include <stdio.h>

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

Compilez et décompilez:

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

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 .textet .rodatadans un segment qui peut être exécuté mais pas modifié ( Flags = R E). Tenter de modifier un tel segment conduit à un segfault sous Linux.

Si nous faisons de même 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.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
22

FYI, juste pour sauvegarder les autres réponses:

La norme: ISO / CEI 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 une durée de stockage statique (3.7)

  2. La question de savoir si tous les littéraux de chaîne sont distincts (c'est-à-dire stockés dans des objets sans chevauchement) est définie par l'implémentation. L'effet de la tentative de modification d'un littéral de chaîne n'est pas défini.

Justicle
la source
2
Informations utiles, mais le lien de notification est pour C ++, alors que la question est liée à c
Grijesh Chauhan
1
confirmé # 2 en 2.13. Avec l'option -Os (optimiser la taille), gcc chevauche les littéraux de chaîne dans .rodata.
Peng Zhang
14

gcc crée une .rodatasection 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 .rdatasection dans le même but.

Vous pouvez consulter la sortie de dumpbinou 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
Alex Budovski
la source
1
Je ne vois pas comment démonter la section rdata avec objdump.
user2284570
@ user2284570, c'est parce que cette section ne contient pas d'assembly. Il contient des données.
Alex Budovski
1
Juste une question pour obtenir une sortie plus lisible. Je veux dire que je voudrais avoir des chaînes en ligne avec le démontage au lieu de l'adresse à ces sections. (ourlet que vous connaissez printf("some null terminated static string");au lieu de printf(*address);C)
user2284570
4

Cela dépend du format de votre exécutable . Une façon d'y penser est que si vous programmiez un assemblage, vous pourriez mettre des littéraux de chaîne dans le segment de données de votre programme d'assemblage. Votre compilateur C fait quelque chose comme ça, mais tout dépend du système pour lequel vous êtes compilé.

Parappa
la source
2

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 une "astuce intelligente" .. Et l'astuce intelligente consiste à "utiliser un pointeur de caractère pointant vers la mémoire" .. rappelez-vous que certains compilateurs, peuvent ne pas permettre cela..Voici la démo

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

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

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

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

Avec gcc sur une machine Windows, et une variable déclarée maincomme

char *c = "whatever";

fonctionnement

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

Retour

Contents of section .rdata:
 0000 77686174 65766572 00000000           whatever....
Mihai
la source