2010-04-07 24 views
130

Me interesa saber dónde se asignan/almacenan los literales de cadena.Literales de cadena: ¿A dónde van?

Lo que encontrar una respuesta intrigante here, diciendo:

Definición de una línea de cadena en realidad incrusta los datos en el programa en sí mismo y no puede ser cambiado (algunos compiladores permiten esto mediante un truco inteligente, no se moleste)

Pero, tenía que ver con C++, sin mencionar que dice no molestarse.

Estoy molestando. = D

Entonces, ¿mi pregunta es dónde y cómo se guarda la cadena literal? ¿Por qué no debería tratar de alterarlo? ¿La implementación varía según la plataforma? ¿Alguien quiere elaborar sobre el "truco inteligente"?

+1

http://stackoverflow.com/questions/ 164194 | http://stackoverflow.com/questions/1704407 –

Respuesta

101

Una técnica común es que los literales de cadena se coloquen en la sección "datos de solo lectura" que se correlaciona en el espacio de proceso como de solo lectura (por lo que no puede cambiarlo).

Varía según la plataforma. Por ejemplo, las arquitecturas de chips más simples pueden no admitir segmentos de memoria de solo lectura, por lo que el segmento de datos será de escritura.

En lugar de tratar de averiguar un truco para hacer que los literales de cadena variable (que será dependiente en su plataforma de alta y podría cambiar con el tiempo), sólo tiene que utilizar matrices:

char foo[] = "..."; 

El compilador se encargará de la matriz para inicializar desde el literal y puede modificar la matriz.

+4

Sí, utilizo matrices cuando quiero tener cadenas mutables. Solo tenía curiosidad. Gracias. –

+2

Sin embargo, debe tener cuidado con el desbordamiento de búfer cuando utiliza matrices para cadenas mutables: simplemente escribir una cadena más larga que la longitud del conjunto (por ejemplo, 'foo =" hello "' en este caso) puede causar efectos colaterales no deseados ... (suponiendo que no está reasignando la memoria con 'nuevo' o algo así) – johnny

+1

¿Al usar una cadena de matriz va en pila o en otro lugar? –

4

Depende del format de su executable. Una forma de pensarlo es que, si fuera la programación de ensamblaje, podría poner literales de cadena en el segmento de datos de su programa de ensamblaje. Su compilador de C hace algo así, pero todo depende de para qué sistema se está compilando el binario.

43

No hay una respuesta a esto. Los estándares C y C++ solo dicen que los literales de cadena tienen una duración de almacenamiento estática, cualquier intento de modificarlos proporciona un comportamiento indefinido, y múltiples literales de cadena con los mismos contenidos pueden o no compartir el mismo almacenamiento.

Según el sistema para el que esté escribiendo y las capacidades del formato de archivo ejecutable que utiliza, pueden almacenarse junto con el código de programa en el segmento de texto, o pueden tener un segmento separado para datos inicializados.

La determinación de los detalles también variará según la plataforma; lo más probable es que incluya herramientas que le indiquen dónde la está colocando. Algunos incluso se dará el control sobre los detalles como que, si lo desea (por ejemplo GNU ld le permite proporcionar una secuencia de comandos para contarlo todo sobre cómo agrupar datos, código, etc.)

+0

No me parece probable que los datos de cadena se almacenen directamente en el segmento .text. Para literales realmente cortos, pude ver el código de generación del compilador como 'movb $ 65, 8 (% esp); movb $ 66, 9 (% esp); movb $ 0, 10 (% esp) 'para la cadena' "AB" ', pero la gran mayoría de las veces, estará en un segmento que no sea de código, como' .data' o '.rodata' o similar (dependiendo de si el destino admite o no segmentos de solo lectura). –

+0

Si los literales de cadena son válidos durante toda la duración del programa, incluso durante la destrucción de objetos estáticos, ¿es válido devolver la referencia constante a un literal de cadena? Por qué este programa muestra un error de tiempo de ejecución, vea http://ideone.com/FTs1Ig – Destructor

13

gcc hace que una sección .rodata se asigna la "algún lugar" en el espacio de direcciones y está marcado de sólo lectura,

Visual C++ (cl.exe) hace que una sección .rdata para el mismo propósito.

Puede consultar la salida de dumpbin o objdump (en Linux) para ver las secciones de su ejecutable.

E.g.

>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

No veo cómo desensamblar la sección de rdata con objdump. – user2284570

+0

@ user2284570, eso es porque esa sección no contiene ensamblaje. Contiene datos. –

+1

Simplemente una cuestión de obtener una salida más legible. Quiero decir que me gustaría incluir cadenas con desmontaje en lugar de direcciones en esas secciones. * (hem you know'printf ("alguna cadena estática terminada nula"); 'en lugar de'printf (* dirección);' en C) * – user2284570

21

su información, simplemente copia de seguridad de las otras respuestas:

El estándar: ISO/IEC 14882:2003 dice:

2,13. Los literales de cadena

  1. [...] Una cadena literal ordinaria tiene tipo “conjunto de n const char” y duración de almacenamiento estático (3,7)

  2. Si todos los literales de cadena son distintos (es decir, son almacenado en objetos no superpuestos) es implementación definida. El efecto de que intenta modificar una cadena literal no está definido.

+1

Información útil, pero el enlace de notificación es para C++, mientras que la pregunta está tangente a [etiqueta : C] –

+1

confirmado # 2 en 2.13. Con la opción -Os (optimizar para el tamaño), gcc solapa literales de cadena en .rodata. –

2

literales de cadena con frecuencia se asignan a la memoria de sólo lectura, haciéndolos inmutables. Sin embargo, en algunos compiladores la modificación es posible mediante un "truco inteligente". Y el truco inteligente es "usar puntero de carácter apuntando a la memoria" ... recuerde algunos compiladores, puede que no permitan esto ... Aquí está la demo

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

¿Por qué está funcionando? –

28

¿Por qué no debería tratar de modificarlo?

Porque es un comportamiento indefinido. Presupuesto de C99 N1256 draft6.7.8/32 "Inicialización":

Ejemplo 8: La declaración

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

define los objetos "simple" matriz de caracteres s y t cuyos elementos se inicializan con literales de cadena de caracteres .

Esta declaración es idéntica a

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

Los contenidos de las matrices son modificables. Por otra parte, la declaración

char *p = "abc"; 

define p con el tipo de "puntero a char" y lo inicializa para apuntar a un objeto con el tipo de "matriz de char" con la longitud de 4 cuyos elementos se inicializan con una cadena de caracteres literal . Si se intenta utilizar p para modificar el contenido de la matriz, el comportamiento no está definido.

¿A dónde van?

GCC 4.8 x86-64 ELF Ubuntu 14.04:

  • char s[]: pila
  • char *s:
    • .rodata sección del fichero objeto
    • el mismo segmento en el que la sección .text del fichero objeto se vierten, lo que ha leído y permisos Exec , pero no Escribir

Programa:

#include <stdio.h> 

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

Compilar y descompilar:

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

salida contiene:

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

Así que la cadena se almacena en la sección .rodata.

Entonces:

readelf -l a.out 

Contiene (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 

Esto significa que la secuencia de comandos enlazador predeterminado vertederos tanto .text y .rodata en un segmento que se puede ejecutar pero sin modificar (Flags = R E) . Intentar modificar dicho segmento conduce a una segfault en Linux.

Si hacemos lo mismo para char[]:

char s[] = "abc"; 

obtenemos:

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

por lo que se almacena en la pila (en relación con %rbp), y podemos por supuesto modificarlo.

0

Como esto podría diferir de compilador para el compilador, la mejor manera es filtrar un volcado de objeto para la cadena buscada literal:

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

donde -s fuerzas objdump para mostrar el contenido completo de todas las secciones, main.o es el archivo de objeto, -B 1 fuerza grep para imprimir una línea antes de la coincidencia (para que pueda ver el nombre de la sección) y str es el literal de cadena que está buscando.

con GCC en una máquina Windows, y una variable declarada en main como

char *c = "whatever"; 

corriendo

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

vuelve

Contents of section .rdata: 
0000 77686174 65766572 00000000   whatever.... 
Cuestiones relacionadas