Literais de string: para onde eles vão?

161

Estou interessado em onde literais de seqüência de caracteres são alocados / armazenados.

Eu encontrei uma resposta intrigante aqui , dizendo:

Definir uma string embutida na verdade incorpora os dados no próprio programa e não pode ser alterado (alguns compiladores permitem isso por um truque inteligente, não se preocupe).

Mas, tinha a ver com C ++, sem mencionar que diz para não se preocupar.

Estou incomodando. = D

Então, minha pergunta é onde e como minha string literal é mantida? Por que não devo tentar alterá-lo? A implementação varia de acordo com a plataforma? Alguém quer elaborar o "truque inteligente"?

Chris Cooper
fonte

Respostas:

125

Uma técnica comum é colocar literais de seqüência de caracteres na seção "dados somente leitura", que é mapeada no espaço do processo como somente leitura (e é por isso que você não pode alterá-los).

Isso varia de acordo com a plataforma. Por exemplo, arquiteturas mais simples de chip podem não suportar segmentos de memória somente leitura, para que o segmento de dados seja gravável.

Em vez disso, tente descobrir um truque para tornar os literais de seqüência de caracteres alteráveis ​​(será altamente dependente da sua plataforma e poderá mudar com o tempo), basta usar matrizes:

char foo[] = "...";

O compilador providenciará a inicialização da matriz a partir do literal e você poderá modificar a matriz.

R Samuel Klatchko
fonte
5
Sim, eu uso matrizes quando quero ter seqüências de caracteres mutáveis. Eu só estava curioso. Obrigado.
Chris Cooper
2
Você precisa ter cuidado com o estouro de buffer ao usar matrizes para seqüências de caracteres mutáveis ​​- basta escrever uma sequência maior que o comprimento da matriz (por exemplo foo = "hello", neste caso) pode causar efeitos colaterais indesejados ... (supondo que você não alocação de memória com newou algo)
johnny
2
Quando o uso de string de matriz entra na pilha ou em outro lugar?
precisa
Não podemos usar char *p = "abc";para criar seqüências de caracteres mutáveis, como dito de forma diferente por @ChrisCooper
KPMG
52

Não há uma resposta para isso. Os padrões C e C ++ dizem apenas que os literais de cadeia de caracteres têm duração estática de armazenamento, qualquer tentativa de modificá-los oferece um comportamento indefinido, e vários literais de cadeia de caracteres com o mesmo conteúdo podem ou não compartilhar o mesmo armazenamento.

Dependendo do sistema para o qual você está escrevendo e dos recursos do formato de arquivo executável usado, eles podem ser armazenados junto com o código do programa no segmento de texto ou podem ter um segmento separado para os dados inicializados.

A determinação dos detalhes também varia dependendo da plataforma - provavelmente incluem ferramentas que podem lhe dizer onde está sendo colocada. Alguns até dão a você controle sobre detalhes como esse, se você desejar (por exemplo, o gnu ld permite que você forneça um script para contar tudo sobre como agrupar dados, códigos, etc.)

Jerry Coffin
fonte
1
Acho improvável que os dados da string sejam armazenados diretamente no segmento .text. Para literais muito curtos, eu podia ver o compilador gerando código, como movb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp)para a string "AB", mas na grande maioria das vezes, ele estará em um segmento que não seja de código, como .dataou .rodataou algo semelhante (dependendo se o destino suporta ou não segmentos somente leitura).
Adam Rosenfield
Se literais de string são válidos por toda a duração do programa, mesmo durante a destruição de objetos estáticos, é válido retornar a referência const a um literal de string? Por que este programa mostra erro de tempo de execução, consulte ideone.com/FTs1Ig
Destructor
@ AdamRosenfield: Se você está entediado em algum momento, pode querer olhar (por exemplo) o formato legado a.out do UNIX (por exemplo, freebsd.org/cgi/… ). Uma coisa que você deve notar rapidamente é que ele suporta apenas um segmento de dados, que é sempre gravável. Portanto, se você quiser literais de seqüência de caracteres somente leitura, essencialmente o único lugar para onde eles podem ir é o segmento de texto (e sim, na época os vinculadores frequentemente faziam exatamente isso).
Jerry Coffin
48

Por que não devo tentar alterá-lo?

Porque é um comportamento indefinido. Citação do projeto C99 N1256 6.7.8 / 32 "Inicialização" :

EXEMPLO 8: A declaração

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

define objetos de matriz de caracteres "simples" se tcujos elementos são inicializados com literais de cadeia de caracteres.

Esta declaração é idêntica à

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

O conteúdo das matrizes é modificável. Por outro lado, a declaração

char *p = "abc";

define pcom o tipo "ponteiro para char" e o inicializa para apontar para um objeto com o tipo "array of char" com comprimento 4 cujos elementos são inicializados com uma cadeia de caracteres literal. Se for feita uma tentativa pde modificar o conteúdo da matriz, o comportamento será indefinido.

Onde eles vão?

GCC 4.8 x86-64 ELF Ubuntu 14.04:

  • char s[]: pilha
  • char *s:
    • .rodata seção do arquivo de objeto
    • o mesmo segmento em que a .textseção do arquivo de objeto é despejada, com permissões de leitura e execução, mas não gravação

Programa:

#include <stdio.h>

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

Compilar e descompilar:

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

A saída contém:

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

Portanto, a string é armazenada na .rodataseção

Então:

readelf -l a.out

Contém (simplificado):

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

Isso significa que o script do vinculador padrão despeja ambos .texte .rodataem um segmento que pode ser executado, mas não modificado ( Flags = R E). Tentar modificar esse segmento leva a um segfault no Linux.

Se fizermos o mesmo para char[]:

 char s[] = "abc";

nós obtemos:

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

para que seja armazenado na pilha (em relação a %rbp) e, é claro, podemos modificá-lo.

Ciro Santilli adicionou uma nova foto
fonte
22

Para sua informação, basta fazer o backup das outras respostas:

O padrão: ISO / IEC 14882: 2003 diz:

2.13 Literais de string

  1. [...] Um literal de string comum tem o tipo "array of n const char" e duração de armazenamento estático (3.7)

  2. Se todas as literais de string são distintas (ou seja, armazenadas em objetos não sobrepostos) é definido pela implementação. O efeito de tentar modificar um literal de cadeia de caracteres é indefinido.

Justicle
fonte
2
Informações úteis, mas link de aviso é para C ++, enquanto questão é com linguetas para c
Grijesh Chauhan
1
confirmou # 2 em 2.13. Com a opção -Os (otimizar para tamanho), o gcc sobrepõe literais de string em .rodata.
Peng Zhang
14

O gcc cria uma .rodataseção que é mapeada "em algum lugar" no espaço de endereço e é marcada como somente leitura,

Visual C ++ ( cl.exe) cria uma .rdataseção para o mesmo propósito.

Você pode ver a saída de dumpbinou objdump(no Linux) para ver as seções do seu executável.

Por exemplo

>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
fonte
1
Não consigo ver como desmontar a seção rdata com o objdump.
user2284570
@ user2284570, é porque essa seção não contém montagem. Ele contém dados.
Alex Budovski
1
Apenas uma questão para obter uma saída mais legível. Quero dizer, gostaria de incluir as seqüências de caracteres com desmontagem em vez de endereçar essas seções. (bainha que você conhece em printf("some null terminated static string");vez de printf(*address);em C) #
222244570
4

Depende do formato do seu executável . Uma maneira de pensar sobre isso é que, se você estivesse programando uma montagem, poderia colocar literais de string no segmento de dados do seu programa de montagem. Seu compilador C faz algo assim, mas tudo depende de qual sistema você está sendo compilado.

Parappa
fonte
2

Literais de seqüência de caracteres são freqüentemente alocados para a memória somente leitura, tornando-os imutáveis. No entanto, em alguns compiladores, a modificação é possível por um "truque inteligente". E o truque inteligente é "usando o ponteiro de caractere apontando para a memória". Lembre-se de alguns compiladores, pode não permitir isso .. Aqui está a demonstração

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

Como isso pode diferir de compilador para compilador, a melhor maneira é filtrar um dump de objeto para o literal da cadeia de caracteres pesquisada:

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

onde -sobriga objdumpa exibir o conteúdo completo de todas as seções, main.oé o arquivo do objeto, -B 1obriga greptambém a imprimir uma linha antes da partida (para que você possa ver o nome da seção) e stré a literal da string que você está procurando.

Com o gcc em uma máquina Windows e uma variável declarada maincomo

char *c = "whatever";

corrida

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

retorna

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