¿Por qué aparece un error de segmentación al escribir en una cadena?
C99 N1256 draft
Hay dos usos diferentes de los literales de cadena de caracteres:
Inicializar char[]
:
char c[] = "abc";
Esto es "más mágico", y se describe en 6.7.8 / 14 "Inicialización":
Una matriz de tipo de caracteres puede ser inicializada por una cadena de caracteres literal, opcionalmente encerrada entre llaves. Los caracteres sucesivos del literal de cadena de caracteres (incluido el carácter nulo de terminación si hay espacio o si la matriz tiene un tamaño desconocido) inicializan los elementos de la matriz.
Entonces esto es solo un atajo para:
char c[] = {'a', 'b', 'c', '\0'};
Al igual que cualquier otra matriz regular, c
se puede modificar.
En todas partes: genera un:
Entonces cuando escribes:
char *c = "abc";
Esto es similar a:
/* __unnamed is magic because modifying it gives UB. */
static char __unnamed[] = "abc";
char *c = __unnamed;
Tenga en cuenta la conversión implícita de char[]
a char *
, que siempre es legal.
Luego, si modifica c[0]
, también modifica __unnamed
, que es UB.
Esto se documenta en 6.4.5 "Literales de cadena":
5 En la fase de traducción 7, se agrega un byte o código de valor cero a cada secuencia de caracteres multibyte que resulta de una cadena literal o literales. La secuencia de caracteres multibyte se usa luego para inicializar una matriz de duración y longitud de almacenamiento estático solo suficiente para contener la secuencia. Para los literales de cadena de caracteres, los elementos de la matriz tienen el tipo char y se inicializan con los bytes individuales de la secuencia de caracteres multibyte [...]
6 No se especifica si estas matrices son distintas siempre que sus elementos tengan los valores apropiados. Si el programa intenta modificar dicha matriz, el comportamiento es indefinido.
6.7.8 / 32 "Inicialización" da un ejemplo directo:
EJEMPLO 8: La declaración
char s[] = "abc", t[3] = "abc";
define objetos de matriz de caracteres "simples" 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 otro lado, la declaración
char *p = "abc";
define p
con 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 p
para modificar el contenido de la matriz, el comportamiento no está definido.
Implementación de GCC 4.8 x86-64 ELF
Programa:
#include <stdio.h>
int main(void) {
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
Conclusión: GCC lo almacena char*
en .rodata
sección, no en .text
.
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
).
Sin embargo , tenga en cuenta que la secuencia de comandos del enlazador predeterminado coloca .rodata
y .text
en el mismo segmento, que tiene permiso de ejecución pero no escritura. Esto se puede observar con:
readelf -l a.out
que contiene:
Section to Segment mapping:
Segment Sections...
02 .text .rodata