¿Por qué aparece un error de segmentación al escribir en una cadena inicializada con "char * s" pero no con "char s []"?
-
03-07-2019 - |
Pregunta
El siguiente código recibe un fallo seg 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.
Solución
Consulte las preguntas frecuentes de C, Pregunta 1.32
Q : ¿Cuál es la diferencia entre estas inicializaciones?
char a [] = " literal de cadena " ;;
char * p = " literal de cadena " ;;
Mi programa se bloquea si intento asignar un nuevo valor ap [i]
.A : literal de cadena (el término formal para una cadena con comillas dobles en C fuente) se puede utilizar en dos ligeramente diferentes formas:
- Como inicializador de una matriz de caracteres, como en la declaración de
char a []
, especifica los valores iniciales de los caracteres en esa matriz (y, si es necesario, su tamaño).- En cualquier otro lugar, se convierte en una matriz estática de caracteres sin nombre, y esta matriz sin nombre puede almacenarse en memoria de solo lectura, y que por lo tanto no necesariamente puede ser modificado. En un contexto de expresión, la matriz se convierte a la vez en un puntero, como de costumbre (ver sección 6), entonces la segunda declaración inicializa p para apuntar a la matriz sin nombre primero elemento.
Algunos compiladores tienen un interruptor controlar si los literales de cadena son editables o no (para compilar viejos código), y algunos pueden tener opciones para hacer que los literales de cadena sean formalmente tratado como matrices de const char (para mejor error de captura).
Otros consejos
Normalmente, los literales de cadena se almacenan en la memoria de solo lectura cuando se ejecuta el programa. Esto es para evitar que cambie accidentalmente una constante de cadena. En su primer ejemplo, " string "
se almacena en la memoria de solo lectura y * str
apunta al primer carácter. El defecto predeterminado ocurre cuando intenta cambiar el primer carácter a 'z'
.
En el segundo ejemplo, la cadena " string "
es copiada por el compilador desde su inicio de solo lectura al str []
matriz. Luego se permite cambiar el primer carácter. Puede verificar esto imprimiendo la dirección de cada uno:
printf("%p", str);
Además, imprimir el tamaño de str
en el segundo ejemplo le mostrará que el compilador le ha asignado 7 bytes:
printf("%d", sizeof(str));
La mayoría de estas respuestas son correctas, pero solo para agregar un poco más de claridad ...
La " memoria de solo lectura " a lo que la gente se refiere es al segmento de texto en términos de 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 apuntar al segmento de texto. Entonces, si intentas cambiarlo, kaboom. Segfault.
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 tales. 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 sigue siendo solo un char *), apunta al segmento de datos en lugar del segmento de texto, que puede modificar de forma segura en tiempo de ejecución.
¿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 magia " ;, 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 encerrado entre llaves. Caracteres sucesivos del literal de cadena de caracteres (incluido el terminando el carácter nulo si hay espacio o si la matriz es de tamaño desconocido) inicialice el 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:
- sin nombre
- matriz de caracteres ¿Cuál es el tipo de literales de cadena 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 la conversión implícita de
char []
achar *
, que siempre es legal.Luego, si modifica
c [0]
, también modifica__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 multibyte secuencia de caracteres que resulta de una cadena literal o literales. El personaje multibyte la secuencia se usa 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 escriba char, y se inicializan con los bytes individuales del carácter multibyte secuencia [...]
6 No se especifica si estas matrices son distintas siempre que sus elementos tengan 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 " simple " objetos de matriz de caracteres
s
yt
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' };
El contenido de las matrices es modificable. Por otro lado, la declaración
char *p = "abc";
define
p
con el tipo "puntero a char" e inicializa para apuntar a un objeto con el tipo "array de char" con longitud 4 cuyos elementos se inicializan con una cadena de caracteres literal. Si se intenta utilizarp
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 char s[] = "abc";
x0,-0x8(%rbp)
f: 00
c: R_X86_64_32S .rodata
Conclusión: GCC almacena char *
en la sección .rodata
, no en .text
.
Si hacemos lo mismo para char []
:
17: c7 45 f0 61 62 63 00 movl readelf -l a.out
x636261,-0x10(%rbp)
obtenemos:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
para que se almacene en la pila (en relación con % rbp
).
Sin embargo, tenga en cuenta que el script de enlace predeterminado coloca .rodata
y .text
en el mismo segmento, que tiene permiso de ejecución pero no de escritura. Esto se puede observar con:
que contiene:
<*>En el primer código, " string " es una constante de cadena, y las constantes de cadena nunca deben modificarse porque a menudo se colocan en la memoria de solo lectura. " str " es un puntero que se usa para modificar la constante.
En el segundo código, " string " es un inicializador de matriz, una especie de mano corta para
char str[7] = { 's', 't', 'r', 'i', 'n', 'g', '\0' };
" str " es una matriz asignada en la pila y se puede modificar libremente.
Porque el tipo de " whatever "
en el contexto del 1er ejemplo es const char *
(incluso si lo asigna a un char no const *) , lo que significa que no deberías intentar escribirle.
El compilador ha forzado esto colocando la cadena en una parte de solo lectura de la memoria, por lo tanto, escribir en ella genera un defecto de seguridad.
Para comprender este error o problema, primero debe conocer la diferencia entre el puntero y la matriz así que aquí, primero, tengo que explicarles las diferencias entre ellas
conjunto de cadenas
char strarray[] = "hello";
En la matriz de memoria se almacena en celdas de memoria continua, almacenada como [h] [e] [l] [l] [o] [\ 0] = > []
es 1 byte de caracteres tamaño de celda de memoria, y a estas celdas de memoria continua se puede acceder por nombre llamado strarray aquí. así que aquí el conjunto de cadenas strarray
contiene todos los caracteres de cadena inicializados en este caso aquí " hello " ;
para que podamos cambiar fácilmente su contenido de memoria accediendo a cada carácter por su valor de índice
`strarray[0]='m'` it access character at index 0 which is 'h'in strarray
y su valor cambió a 'm'
por lo que el valor de strarray cambió a " mello "
;
un punto para tener en cuenta aquí que podemos cambiar el contenido de la matriz de cadenas cambiando carácter por carácter, pero no podemos inicializar otra cadena directamente como strarray = " cadena nueva "
no es válida
Puntero
Como todos sabemos, el puntero apunta a la ubicación de la memoria en la memoria, el puntero no inicializado apunta a una ubicación de memoria aleatoria y, después de la inicialización, apunta a una ubicación de memoria particular
char *ptr = "hello";
aquí el puntero ptr se inicializa en la cadena " hello "
que es una cadena constante almacenada en la memoria de solo lectura (ROM), por lo que " hello "
no se puede cambiar como se almacena en ROM
y ptr se almacena en la sección de pila y apunta a una cadena constante "hello"
entonces ptr [0] = 'm' no es válido ya que no puede acceder a la memoria de solo lectura
Pero ptr se puede inicializar a otro valor de cadena directamente ya que es solo un puntero, por lo que puede apuntar a cualquier dirección de memoria de variable de su tipo de datos
ptr="new string"; is valid
char *str = "string";
Lo anterior establece str
para apuntar al valor literal " string "
que está codificado en la imagen binaria del programa, que probablemente esté marcado como de solo lectura en memoria.
Entonces str [0] =
está intentando escribir en el código de solo lectura de la aplicación. Supongo que esto probablemente depende del compilador.
char *str = "string";
asigna un puntero a un literal de cadena, que el compilador está colocando en una parte no modificable de su ejecutable;
char str[] = "string";
asigna e inicializa una matriz local que es modificable
Las preguntas frecuentes de C a las que @matli se vinculó lo mencionan, pero nadie más lo ha hecho todavía, así que para aclarar: si una cadena literal (cadena de doble comilla en su fuente) se usa en cualquier lugar que no sea para inicializar una matriz de caracteres (es decir: el segundo ejemplo de @ Mark, que funciona correctamente), esa cadena es almacenada por el compilador en una tabla de cadenas estática especial , que es similar a crear una variable estática global ( solo lectura, por supuesto) que es esencialmente anónimo (no tiene variable "nombre"). La parte solo lectura es la parte importante, y es la razón por la cual el primer ejemplo de código de @ Mark se daña.
El
char *str = "string";
La línea define un puntero y lo apunta a una cadena literal. La cadena literal no se puede escribir, así que cuando lo haga:
str[0] = 'z';
obtienes una falla seg. En algunas plataformas, el literal puede estar en la memoria de escritura, por lo que no verá un segfault, pero es un código no válido (que resulta en un comportamiento indefinido) independientemente.
La línea:
char str[] = "string";
asigna una matriz de caracteres y copia la cadena literal en esa matriz, que es totalmente grabable, por lo que la actualización posterior no es un problema.
Literales de cadena como " cadena " probablemente se asignan en el espacio de direcciones de su ejecutable como datos de solo lectura (más o menos su compilador). Cuando lo tocas, se asusta de que estés en su área de traje de baño y te avisa con una falla seg.
En su primer ejemplo, obtendrá un puntero a los datos constantes. En su segundo ejemplo, está inicializando una matriz de 7 caracteres con una copia de los datos constantes.
// create a string constant like this - will be read only
char *str_p;
str_p = "String constant";
// create an array of characters like this
char *arr_p;
char arr[] = "String in an array";
arr_p = &arr[0];
// now we try to change a character in the array first, this will work
*arr_p = 'E';
// lets try to change the first character of the string contant
*str_p = 'G'; // this will result in a segmentation fault. Comment it out to work.
/*-----------------------------------------------------------------------------
* String constants can't be modified. A segmentation fault is the result,
* because most operating systems will not allow a write
* operation on read only memory.
*-----------------------------------------------------------------------------*/
//print both strings to see if they have changed
printf("%s\n", str_p); //print the string without a variable
printf("%s\n", arr_p); //print the string, which is in an array.
En primer lugar, str
es un puntero que apunta a " string "
. El compilador puede colocar literales de cadena en lugares en la memoria en los que no puede escribir, pero solo puede leer. (Esto realmente debería haber provocado una advertencia, ya que está asignando un const char *
a un char *
. ¿Ha deshabilitado las advertencias o simplemente las ignoró? )
En segundo lugar, está creando una matriz, que es la memoria a la que tiene acceso completo, e inicializándola con " string "
. Estás creando un char [7]
(seis para las letras, uno para la terminación '\ 0'), y haces lo que quieras con él.
Suponga que las cadenas son,
char a[] = "string literal copied to stack";
char *p = "string literal referenced by p";
En el primer caso, el literal debe copiarse cuando 'a' entra en el alcance. Aquí 'a' es una matriz definida en la pila. Significa que la cadena se creará en la pila y sus datos se copian de la memoria de código (texto), que generalmente es de solo lectura (esto es específico de la implementación, un compilador puede colocar estos datos de programa de solo lectura en la memoria de lectura y escritura también ).
En el segundo caso, p es un puntero definido en la pila (ámbito local) y que hace referencia a un literal de cadena (datos de programa o texto) almacenado en otro lugar. Por lo general, modificar dicha memoria no es una buena práctica ni se recomienda.
Primero hay una cadena constante que no se puede modificar. El segundo es una matriz con valor inicializado, por lo que se puede modificar.
se produce cuando intenta acceder a la memoria que no es accesible.
char * str
es un puntero a una cadena que no es modificable (la razón por la que se produce la falla seg) ..
mientras que char str []
es una matriz y puede ser modificable ..