¿Por qué aparece un error de segmentación al escribir en una cadena inicializada con "char * s" pero no con "char s []"?

StackOverflow https://stackoverflow.com/questions/164194

  •  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.

¿Fue útil?

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 a p [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:

     
      
  1. 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).
  2.   
  3. 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.
  4.   
     

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:

  1. 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.

  2. 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 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 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' };
     

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 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   
 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.

El error de segmentación

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 ..

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top