![¿Por qué obtengo una falla de segmentación cuando escribo en un "char *s" inicializado con un literal de cadena, pero no "char s[]"? 1 avatar de usuario](https://www.revivemyvote.com/wp-content/uploads/¿Por-que-obtengo-una-falla-de-segmentacion-cuando-escribo-en.png)
Marcos
El siguiente código recibe una falla de segmento en la línea 2:
char *str = "string";
str[0] = 'z'; // could be also written as *str="z"
printf("%s\n", str);
Si bien esto funciona perfectamente bien:
char str[] = "string";
str[0] = 'z';
printf("%s\n", str);
Probado con MSVC y GCC.
![¿Por qué obtengo una falla de segmentación cuando escribo en un "char *s" inicializado con un literal de cadena, pero no "char s[]"? 2 avatar de usuario](https://www.revivemyvote.com/wp-content/uploads/¿Por-que-obtengo-una-falla-de-segmentacion-cuando-escribo-en.jpg)
Ciro Santilli Путлер Капут 六四事
¿Por qué obtengo una falla de segmentación cuando escribo en una cadena?
Proyecto C99 N1256
Hay dos usos diferentes de los literales de cadenas 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 carácter puede inicializarse mediante una cadena de caracteres literal, opcionalmente encerrada entre llaves. Los caracteres sucesivos del literal de la 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.
Así que esto es solo un atajo para:
char c[] = {'a', 'b', 'c', '\0'};
Como cualquier otra matriz regular, c
se puede modificar
-
En cualquier otro lugar: genera un:
- sin nombre
- array of char ¿Cuál es el tipo de cadenas literales en C y C++?
- con almacenamiento estático
- que da UB si se modifica
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 el elenco implícito de char[]
para char *
que siempre es legal.
Entonces si modificas c[0]
también modificas __unnamed
que es UB.
Esto está documentado 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 de almacenamiento estático y la longitud suficiente para contener la secuencia. Para los literales de cadenas 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 una matriz de este tipo, el comportamiento no está definido.
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 cadenas de caracteres.
Esta declaración es idéntica a
char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };
El contenido de las matrices es modificable. Por otra parte, la declaración
char *p = "abc";
define p
con tipo “puntero a char” y lo inicializa para apuntar a un objeto con 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: tiendas GCC 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 el script del enlazador predeterminado pone .rodata
y .text
en el mismo segmento, que tiene permiso de ejecución pero no de escritura. Esto se puede observar con:
readelf -l a.out
que contiene:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
La mayoría de estas respuestas son correctas, pero solo para agregar un poco más de claridad…
La “memoria de solo lectura” a la que se refiere la gente es el segmento de texto en términos ASM. Es el mismo lugar en la memoria donde se cargan las instrucciones. Esto es de solo lectura por razones obvias como la seguridad. Cuando crea un char* inicializado en una cadena, los datos de la cadena se compilan en el segmento de texto y el programa inicializa el puntero para que apunte al segmento de texto. Entonces, si intentas cambiarlo, kaboom. Fallo de segmento.
Cuando se escribe como una matriz, el compilador coloca los datos de cadena inicializados en el segmento de datos, que es el mismo lugar donde viven sus variables globales y demás. Esta memoria es mutable, ya que no hay instrucciones en el segmento de datos. Esta vez, cuando el compilador inicializa la matriz de caracteres (que aún es solo un carácter *), apunta al segmento de datos en lugar del segmento de texto, que puede modificar de manera segura en tiempo de ejecución.
![¿Por qué obtengo una falla de segmentación cuando escribo en un "char *s" inicializado con un literal de cadena, pero no "char s[]"? 3 avatar de usuario](https://www.revivemyvote.com/wp-content/uploads/1649929517_831_¿Por-que-obtengo-una-falla-de-segmentacion-cuando-escribo-en.png)
Bence Kaulics
char *str = "string";
Los conjuntos anteriores str
para señalar el valor literal "string"
que está codificado en la imagen binaria del programa, que probablemente esté marcado como de solo lectura en la memoria.
Asi que str[0]=
está intentando escribir en el código de solo lectura de la aplicación. Sin embargo, supongo que esto probablemente depende del compilador.
memoria constante
Dado que los literales de cadena son de solo lectura por diseño, se almacenan en el parte constante de la memoria Los datos almacenados allí son inmutables, es decir, no se pueden cambiar. Por lo tanto, todos los literales de cadena definidos en el código C obtienen aquí una dirección de memoria de solo lectura.
memoria de pila
Él parte de la pila de memoria es donde residen las direcciones de las variables locales, por ejemplo, variables definidas en funciones.
Como sugiere la respuesta de @matli, hay dos formas de trabajar con cadenas de estas cadenas constantes.
1. Puntero a literal de cadena
Cuando definimos un puntero a un literal de cadena, estamos creando una variable de puntero que vive en memoria de pila. Apunta a la dirección de solo lectura donde reside el literal de cadena subyacente.
#include <stdio.h>
int main(void) {
char *s = "hello";
printf("%p\n", &s); // Prints a read-only address, e.g. 0x7ffc8e224620
return 0;
}
Si tratamos de modificar s
insertando
s[0] = 'H';
obtenemos un Segmentation fault (core dumped)
. Estamos tratando de acceder a la memoria a la que no deberíamos acceder. Estamos intentando modificar el valor de una dirección de solo lectura, 0x7ffc8e224620
.
2. Matriz de caracteres
Por el bien del ejemplo, supongamos que el literal de cadena "Hello"
almacenado en la memoria constante tiene una dirección de memoria de solo lectura idéntica a la anterior, 0x7ffc8e224620
.
#include <stdio.h>
int main(void) {
// We create an array from a string literal with address 0x7ffc8e224620.
// C initializes an array variable in the stack, let's give it address
// 0x7ffc7a9a9db2.
// C then copies the read-only value from 0x7ffc8e224620 into
// 0x7ffc7a9a9db2 to give us a local copy we can mutate.
char a[] = "hello";
// We can now mutate the local copy
a[0] = 'H';
printf("%p\n", &a); // Prints the Stack address, e.g. 0x7ffc7a9a9db2
printf("%s\n", a); // Prints "Hello"
return 0;
}
Nota: Cuando se usan punteros para cadenas de literales como en 1., la mejor práctica es usar el const
palabra clave, como const *s = "hello"
. Esto es más legible y el compilador proporcionará una mejor ayuda cuando se viole. Luego arrojará un error como error: assignment of read-only location ‘*s’
en lugar de la falla seg. Es probable que los linters en los editores detecten el error antes de compilar manualmente el código.
Es divertido, pero en realidad se compila y se ejecuta perfectamente cuando se usa el compilador de Windows (cl) en un símbolo del sistema de desarrollador de Visual Studio. Me confundí por unos momentos…
– Suricata inconformista
13 de septiembre de 2016 a las 8:30