Letterali a corda: dove vanno?


161

Sono interessato a dove vengono allocati / memorizzati i letterali stringa.

Ho trovato una risposta interessante qui , dicendo:

La definizione di una stringa in linea in realtà incorpora i dati nel programma stesso e non può essere modificata (alcuni compilatori lo consentono con un trucco intelligente, non preoccuparti).

Ma aveva a che fare con il C ++, per non parlare del fatto che dice di non disturbare.

Mi sto preoccupando. = D

Quindi la mia domanda è dove e come viene mantenuta la mia stringa letterale? Perché non dovrei provare a modificarlo? L'implementazione varia in base alla piattaforma? Qualcuno si preoccupa di elaborare il "trucco intelligente"?

Risposte:


125

Una tecnica comune prevede l'inserimento di valori letterali stringa nella sezione "dati di sola lettura" che viene mappata nello spazio del processo in sola lettura (motivo per cui non è possibile modificarlo).

Varia in base alla piattaforma. Ad esempio, architetture di chip più semplici potrebbero non supportare segmenti di memoria di sola lettura, quindi il segmento di dati sarà scrivibile.

Piuttosto, prova a trovare un trucco per rendere modificabili i valori letterali delle stringhe (dipenderà molto dalla tua piattaforma e potrebbe cambiare nel tempo), usa solo array:

char foo[] = "...";

Il compilatore organizzerà l'inizializzazione dell'array dal valore letterale e sarà possibile modificarlo.


5
Sì, utilizzo le matrici quando voglio avere stringhe mutabili. Ero solo curioso. Grazie.
Chris Cooper,

2
Dovete stare attenti a buffer overflow quando si utilizzano le matrici per le stringhe mutevoli, anche se - semplicemente scrivendo una stringa più lunga della lunghezza della matrice (ad esempio, foo = "hello"in questo caso) può causare effetti collaterali indesiderati ... (supponendo che non sei ri- allocare memoria con newo qualcosa del genere)
johnny,

2
Quando si utilizza la stringa di array va in pila o altrove?
Suraj Jain,

Non possiamo usare char *p = "abc";per fare le stringhe mutevoli come diversamente detto da @ChrisCooper
KPMG

52

Non c'è una risposta a questo. Gli standard C e C ++ affermano solo che i valori letterali di stringa hanno una durata di archiviazione statica, qualsiasi tentativo di modificarli comporta un comportamento indefinito e che più valori letterali di stringa con lo stesso contenuto possono o meno condividere lo stesso archivio.

A seconda del sistema per cui si sta scrivendo e delle capacità del formato di file eseguibile che utilizza, potrebbero essere memorizzati insieme al codice del programma nel segmento di testo oppure potrebbero avere un segmento separato per i dati inizializzati.

Determinare i dettagli varierà anche a seconda della piattaforma - molto probabilmente includono strumenti che possono dirti dove lo sta mettendo. Alcuni ti daranno anche il controllo di dettagli come quello, se lo desideri (ad esempio, gnu ld ti consente di fornire uno script per dire tutto su come raggruppare dati, codice, ecc.)


1
Trovo improbabile che i dati della stringa vengano memorizzati direttamente nel segmento .text. Per letterali davvero brevi, ho potuto vedere il compilatore generare codice come movb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp)per la stringa "AB", ma la maggior parte delle volte, sarà in un segmento non di codice come .datao .rodatao simili (a seconda che il target supporti o meno segmenti di sola lettura).
Adam Rosenfield,

Se i valori letterali di stringa sono validi per l'intera durata del programma, anche durante la distruzione di oggetti statici, è valido restituire il riferimento const a un valore letterale di stringa? Perché questo programma mostra errori di runtime, vedi ideone.com/FTs1Ig
Destructor il

@AdamRosenfield: se ti annoi qualche volta, potresti voler guardare (per un esempio) il formato UNIX a.out legacy (ad esempio, freebsd.org/cgi/… ). Una cosa che dovresti notare rapidamente è che supporta solo un segmento di dati, che è sempre scrivibile. Quindi, se vuoi letterali stringa di sola lettura, essenzialmente l'unico posto in cui possono andare è il segmento di testo (e sì, al momento i linker hanno fatto esattamente quello).
Jerry Coffin

48

Perché non dovrei provare a modificarlo?

Perché è un comportamento indefinito. Citazione da C99 N1256 bozza 6.7.8 / 32 "Inizializzazione" :

ESEMPIO 8: La dichiarazione

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

definisce oggetti "semplici" di array di caratteri se i tcui elementi sono inizializzati con valori letterali di stringa di caratteri.

Questa dichiarazione è identica a

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

Il contenuto degli array è modificabile. D'altra parte, la dichiarazione

char *p = "abc";

definisce pcon il tipo "pointer to char" e lo inizializza in modo che punti a un oggetto con tipo "array of char" con lunghezza 4 i cui elementi sono inizializzati con una stringa di caratteri letterale. Se si tenta di utilizzare pper modificare il contenuto dell'array, il comportamento non è definito.

Dove vanno?

GCC 4.8 x86-64 ELF Ubuntu 14.04:

  • char s[]: stack
  • char *s:
    • .rodata sezione del file oggetto
    • lo stesso segmento in cui .textviene scaricata la sezione del file oggetto, che dispone delle autorizzazioni di lettura ed Exec, ma non di scrittura

Programma:

#include <stdio.h>

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

Compilare e decompilare:

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

L'output contiene:

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

Quindi la stringa è memorizzata nella .rodatasezione.

Poi:

readelf -l a.out

Contiene (semplificato):

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

Ciò significa che lo script del linker predefinito esegue il dump di entrambi .texte .rodatain un segmento che può essere eseguito ma non modificato ( Flags = R E). Il tentativo di modificare un tale segmento porta a un segfault in Linux.

Se facciamo lo stesso per char[]:

 char s[] = "abc";

otteniamo:

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

quindi viene memorizzato nello stack (rispetto a %rbp) e ovviamente possiamo modificarlo.


22

Cordiali saluti, solo il backup delle altre risposte:

Lo standard: ISO / IEC 14882: 2003 afferma:

2.13. Letterali a corda

  1. [...] Un letterale stringa normale ha tipo "array of n const char" e durata di memorizzazione statica (3.7)

  2. Se tutti i letterali di stringa sono distinti (ovvero, sono memorizzati in oggetti non sovrapposti) è definito dall'implementazione. L'effetto del tentativo di modificare una stringa letterale non è definito.


2
Informazioni utili, ma il collegamento di avviso è per C ++, mentre la domanda è regolata in c
Grijesh Chauhan,

1
# 2 confermato in 2.13. Con l'opzione -Os (ottimizza per dimensioni), gcc si sovrappone ai letterali di stringa in .rodata.
Peng Zhang,

14

gcc crea una .rodatasezione che viene mappata "da qualche parte" nello spazio degli indirizzi ed è contrassegnata come sola lettura,

Visual C ++ ( cl.exe) crea una .rdatasezione per lo stesso scopo.

Puoi vedere l'output da dumpbino objdump(su Linux) per vedere le sezioni del tuo eseguibile.

Per esempio

>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

1
Non riesco a vedere come smontare la sezione rdata con objdump.
user2284570

@ user2284570, questo perché quella sezione non contiene assembly. Contiene dati.
Alex Budovski,

1
È solo questione di ottenere un output più leggibile. Voglio dire, vorrei che le stringhe fossero allineate con lo smontaggio anziché l'indirizzo di quelle sezioni. (orlo che conosci printf("some null terminated static string");invece che printf(*address);in C)
user2284570

4

Dipende dal formato del tuo eseguibile . Un modo di pensarci è che se stavi programmando un assembly, potresti mettere letterali di stringa nel segmento di dati del tuo programma di assemblaggio. Il tuo compilatore C fa qualcosa del genere, ma tutto dipende dal sistema per cui stai compilando il tuo binario.


2

I letterali stringa sono spesso allocati nella memoria di sola lettura, rendendoli immutabili. Tuttavia, in alcuni compilatori la modifica è possibile con un "trucco intelligente" .. E il trucco intelligente è "usando il puntatore del carattere che punta alla memoria" .. ricorda alcuni compilatori, potrebbe non consentire questo ... Ecco la demo

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

0

Poiché ciò potrebbe differire da compilatore a compilatore, il modo migliore è filtrare un dump di oggetti per la stringa cercata letterale:

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

dove -sforza la objdumpvisualizzazione del contenuto completo di tutte le sezioni, main.oè il file oggetto, -B 1forza grepanche a stampare una riga prima della corrispondenza (in modo da poter vedere il nome della sezione) ed strè la stringa letterale che stai cercando.

Con gcc su un computer Windows e una variabile dichiarata in mainlike

char *c = "whatever";

in esecuzione

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

ritorna

Contents of section .rdata:
 0000 77686174 65766572 00000000           whatever....
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.