Literales de cadena: ¿A dónde van?

161

Estoy interesado en dónde se asignan / almacenan los literales de cadena.

Encontré una respuesta intrigante aquí , diciendo:

Definir una cadena en línea en realidad incrusta los datos en el propio programa y no se puede cambiar (algunos compiladores lo permiten mediante un truco inteligente, no te molestes).

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 mantiene mi cadena literal. ¿Por qué no debería intentar alterarlo? ¿La implementación varía según la plataforma? ¿Alguien quiere explicar el "truco inteligente"?

Chris Cooper
fuente

Respuestas:

125

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

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

En su lugar, intente descubrir un truco para hacer que los literales de cadena cambien (dependerá en gran medida de su plataforma y podría cambiar con el tiempo), solo use matrices:

char foo[] = "...";

El compilador se encargará de que la matriz se inicialice desde el literal y usted puede modificarla.

R Samuel Klatchko
fuente
55
Sí, uso matrices cuando quiero tener cadenas mutables. Solo tenía curiosidad. Gracias.
Chris Cooper
2
Sin embargo, debe tener cuidado con el desbordamiento del búfer cuando use matrices para cadenas mutables: simplemente escribir una cadena más larga que la longitud de la matriz (por ejemplo, foo = "hello"en este caso) puede causar efectos secundarios no deseados ... (suponiendo que no esté rea asignar memoria con newo algo)
Johnny
2
¿Al usar una cadena de matriz va en la pila o en otro lugar?
Suraj Jain
¿No podemos usar char *p = "abc";para hacer que las cadenas mutables como se ha dicho de manera diferente por @ChrisCooper
KPMG
52

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

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

La determinación de los detalles también variará dependiendo de la plataforma; lo más probable es que incluyan herramientas que puedan indicarle dónde se encuentra. Algunos incluso le darán control sobre detalles como ese, si lo desea (por ejemplo, gnu ld le permite proporcionar un script para contarle todo sobre cómo agrupar datos, código, etc.)

Jerry Coffin
fuente
1
Me parece poco probable que los datos de la cadena se almacenen directamente en el segmento .text. Para literales realmente cortos, podría ver el compilador generando código 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 sin código como .datao .rodatao similar (dependiendo de si el destino admite o no segmentos de solo lectura).
Adam Rosenfield
Si los literales de cadena son válidos durante todo el programa, incluso durante la destrucción de objetos estáticos, ¿es válido devolver una referencia constante a un literal de cadena? Por qué este programa muestra un error de tiempo de ejecución, vea ideone.com/FTs1Ig
Destructor el
@AdamRosenfield: Si alguna vez estás aburrido, es posible que desees ver (por ejemplo) el formato heredado de UNIX a.out (por ejemplo, freebsd.org/cgi/… ). Una cosa que debe notar rápidamente es que solo admite un segmento de datos, que siempre se puede escribir. Entonces, si desea literales de cadena de solo lectura, esencialmente el único lugar al que pueden ir es el segmento de texto (y sí, en el momento en que los enlazadores con frecuencia hacían exactamente eso).
Jerry Coffin
48

¿Por qué no debería intentar alterarlo?

Porque es un comportamiento indefinido. Cita del C99 N1256 borrador 6.7.8 / 32 "Inicialización" :

EJEMPLO 8: La declaración

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

define objetos de matriz de caracteres "simples" syt 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 otro lado, la declaración

char *p = "abc";

se define pcon el tipo "puntero a char" y lo inicializa para apuntar a un objeto con el tipo "matriz de char" con longitud 4 cuyos elementos se inicializan con una cadena de caracteres literal. Si se intenta utilizar ppara 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[]: apilar
  • char *s:
    • .rodata sección del archivo objeto
    • el mismo segmento donde se volca la .textsección del archivo objeto, que tiene permisos de lectura y ejecución, pero no escritura

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

La 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

Entonces la cadena se almacena en el .rodata sección.

Luego:

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 .texty .rodataen un segmento que se puede ejecutar pero sin modificar ( Flags = R E). Intentar modificar un segmento de este tipo conduce a un defecto 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, por supuesto, podemos modificarlo.

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

FYI, solo respaldando las otras respuestas:

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

2.13. Literales de cadena

  1. [...] Un literal de cadena normal tiene el tipo "matriz de n const char" y la duración del almacenamiento estático (3.7)

  2. Si todos los literales de cadena son distintos (es decir, si están almacenados en objetos no superpuestos) se define la implementación. El efecto de intentar modificar un literal de cadena no está definido.

Justicle
fuente
2
Información útil, pero el enlace de aviso es para C ++, mientras que la pregunta está vinculada a c
Grijesh Chauhan
1
confirmado # 2 en 2.13. Con la opción -Os (optimizar para el tamaño), gcc se superpone a los literales de cadena en .rodata.
Peng Zhang
14

gcc crea una .rodatasección que se asigna "en algún lugar" en el espacio de direcciones y está marcada como de solo lectura,

Visual C ++ ( cl.exe) hace un.rdata sección con el mismo propósito.

Puede mirar la salida desde dumpbinoobjdump (en Linux) para ver las secciones de su ejecutable.

P.ej

>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
fuente
1
No puedo ver cómo desmontar la sección rdata con objdump.
user2284570
@ user2284570, eso se debe a que esa sección no contiene ensamblaje. Contiene datos.
Alex Budovski
1
Solo es cuestión de obtener resultados más legibles. Quiero decir que me gustaría alinear las cadenas con el desmontaje en lugar de dirigirlas a esas secciones. (hem sabes en printf("some null terminated static string");lugar de printf(*address);en C)
user2284570
4

Depende del formato de su ejecutable . Una forma de pensarlo es que si estuviera programando ensamblaje, podría colocar literales de cadena en el segmento de datos de su programa ensamblador. Su compilador de C hace algo así, pero todo depende de para qué sistema se está compilando el binario.

Parappa
fuente
2

Los literales de cadena se asignan con frecuencia a la memoria de solo lectura, lo que los hace inmutables. Sin embargo, en algunos compiladores la modificación es posible mediante un "truco inteligente" ... Y el truco inteligente es "usar el puntero de caracteres que apunta a la memoria" ... recuerde que algunos compiladores pueden no permitir esto ... Aquí está la demostración

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

Como esto puede diferir de un compilador a otro, la mejor manera es filtrar un volcado de objeto para el literal de cadena buscado:

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

donde -sobliga objdumpa mostrar el contenido completo de todas las secciones, main.oes el archivo objeto, -B 1obliga grepa imprimir también una línea antes de la coincidencia (para que pueda ver el nombre de la sección) ystr es el literal de cadena que está buscando.

Con gcc en una máquina con Windows, y una variable declarada en mainlike

char *c = "whatever";

corriendo

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

devoluciones

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