En C, se puede usar un literal de cadena en una declaración como esta:
char s[] = "hello";
o así:
char *s = "hello";
Entonces cuál es la diferencia? Quiero saber qué sucede realmente en términos de duración de almacenamiento, tanto en tiempo de compilación como de ejecución.
Respuestas:
La diferencia aquí es que
colocará
"Hello world"
en las partes de solo lectura de la memoria , y haciendos
un puntero a eso, cualquier operación de escritura en esta memoria es ilegal.Mientras se hace:
coloca la cadena literal en la memoria de solo lectura y copia la cadena en la memoria recién asignada en la pila. Haciendo así
legal.
fuente
"Hello world"
está en "partes de solo lectura de la memoria" en ambos ejemplos. El ejemplo con la matriz apunta allí, el ejemplo con la matriz copia los caracteres a los elementos de la matriz.char msg[] = "hello, world!";
la cadena que termina en la sección de datos inicializados. Cuando se declarachar * const
que termina en la sección de datos de solo lectura. gcc-4.5.3En primer lugar, en argumentos de función, son exactamente equivalentes:
En otros contextos,
char *
asigna un puntero, mientras quechar []
asigna una matriz. ¿Dónde va la cuerda en el primer caso, preguntas? El compilador asigna secretamente una matriz anónima estática para contener el literal de cadena. Entonces:Tenga en cuenta que nunca debe intentar modificar el contenido de esta matriz anónima a través de este puntero; los efectos son indefinidos (a menudo significan un bloqueo):
El uso de la sintaxis de la matriz lo asigna directamente a la nueva memoria. Por lo tanto, la modificación es segura:
Sin embargo, la matriz solo vive mientras su alcance de contagio, por lo que si hace esto en una función, no devuelva ni pierda un puntero a esta matriz; haga una copia en su lugar con
strdup()
o similar. Si la matriz se asigna en alcance global, por supuesto, no hay problema.fuente
Esta declaración:
Crea un objeto: una
char
matriz de tamaño 6, llamadas
, inicializada con los valores'h', 'e', 'l', 'l', 'o', '\0'
. La ubicación de esta matriz en la memoria y el tiempo de vida depende de dónde aparezca la declaración. Si la declaración está dentro de una función, vivirá hasta el final del bloque en el que se declara, y casi con certeza se asignará en la pila; si está fuera de una función, probablemente se almacenará dentro de un "segmento de datos inicializado" que se carga desde el archivo ejecutable en la memoria grabable cuando se ejecuta el programa.Por otro lado, esta declaración:
Crea dos objetos:
char
s que contiene los valores'h', 'e', 'l', 'l', 'o', '\0'
, que no tiene nombre y tiene una duración de almacenamiento estático (lo que significa que dura toda la vida del programa); ys
, que se inicializa con la ubicación del primer carácter en esa matriz de solo lectura sin nombre.La matriz de solo lectura sin nombre generalmente se encuentra en el segmento de "texto" del programa, lo que significa que se carga desde el disco en la memoria de solo lectura, junto con el código mismo. La ubicación de la
s
variable del puntero en la memoria depende de dónde aparece la declaración (como en el primer ejemplo).fuente
char s[] = "hello"
caso,"hello"
es solo un inicializador que le dice al compilador cómo se debe inicializar la matriz. Puede o no dar como resultado una cadena correspondiente en el segmento de texto; por ejemplo, sis
tiene una duración de almacenamiento estático, es probable que la única instancia de"hello"
esté en el segmento de datos inicializado: el objeto ens
sí. Incluso sis
tiene una duración de almacenamiento automática, puede inicializarse mediante una secuencia de almacenes literales en lugar de una copia (p. Ej.movl $1819043176, -6(%ebp); movw $111, -2(%ebp)
)..rodata
cual el script del enlazador luego se descarga en el mismo segmento que.text
. Mira mi respuesta .char s[] = "Hello world";
coloca la cadena literal en la memoria de solo lectura y copia la cadena a la memoria recién asignada en la pila. Sin embargo, su respuesta sólo se habla de la opción de venta literal de cadena en la memoria de sólo lectura y se salta la segunda parte de la frase que dice:copies the string to newly allocated memory on the stack
. Entonces, ¿su respuesta es incompleta por no especificar la segunda parte?char s[] = "Hellow world";
es solo un inicializador y no se almacena necesariamente como una copia de solo lectura separada. Sis
tiene una duración de almacenamiento estático, es probable que la única copia de la cadena esté en un segmento de lectura-escritura en la ubicación des
, e incluso si no, el compilador puede optar por inicializar la matriz con instrucciones de carga inmediata o similar en lugar de copiar de una cadena de solo lectura. El punto es que en este caso, la cadena inicializadora en sí misma no tiene presencia en tiempo de ejecución.Dadas las declaraciones
suponga el siguiente mapa de memoria hipotético:
El literal de cadena
"hello world"
es una matriz de 12 elementoschar
(const char
en C ++) con una duración de almacenamiento estático, lo que significa que la memoria para él se asigna cuando se inicia el programa y permanece asignada hasta que finaliza el programa. Intentar modificar el contenido de un literal de cadena invoca un comportamiento indefinido.La línea
se define
s0
como un punterochar
con una duración de almacenamiento automático (lo que significa que la variables0
solo existe para el alcance en el que se declara) y le copia la dirección del literal de cadena (0x00008000
en este ejemplo). Tenga en cuenta que yas0
apunta a un literal de cadena, no debe ser utilizado como un argumento a cualquier función que se trate de modificarlo (por ejemplo,strtok()
,strcat()
,strcpy()
, etc.).La línea
se define
s1
como una matriz de 12 elementos dechar
(la longitud se toma del literal de cadena) con duración de almacenamiento automático y copia el contenido del literal a la matriz. Como puede ver en el mapa de memoria, tenemos dos copias de la cadena"hello world"
; la diferencia es que puedes modificar la cadena contenida ens1
.s0
ys1
son intercambiables en la mayoría de los contextos; Aquí están las excepciones:Puede reasignar la variable
s0
para que apunte a un literal de cadena diferente u otra variable. No puede reasignar la variables1
para que apunte a una matriz diferente.fuente
C99 N1256 draft
Hay dos usos diferentes de los literales de cadena de caracteres:
Inicializar
char[]
:Esto es "más mágico", y se describe en 6.7.8 / 14 "Inicialización":
Entonces esto es solo un atajo para:
Al igual que cualquier otra matriz regular,
c
se puede modificar.En todas partes: genera un:
Entonces cuando escribes:
Esto es similar a:
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 se documenta en 6.4.5 "Literales de cadena":
6.7.8 / 32 "Inicialización" da un ejemplo directo:
Implementación de GCC 4.8 x86-64 ELF
Programa:
Compilar y descompilar:
La salida contiene:
Conclusión: GCC lo almacena
char*
en.rodata
sección, no en.text
.Sin embargo , tenga en cuenta que la secuencia de comandos del vinculador predeterminado coloca
.rodata
y.text
en el mismo segmento , que tiene permiso de ejecución pero no escritura. Esto se puede observar con:que contiene:
Si hacemos lo mismo para
char[]
:obtenemos:
por lo que se almacena en la pila (en relación con
%rbp
).fuente
declara
s
ser una matrizchar
cuyo tiempo es suficiente para contener el inicializador (5 + 1char
s) e inicializa la matriz copiando los miembros de la cadena literal literal en la matriz.declara
s
ser un puntero a uno o más (en este caso más)char
sy lo apunta directamente a una ubicación fija (solo lectura) que contiene el literal"hello"
.fuente
s
es un puntero aconst char
.Aquí
s
hay una serie de caracteres que se pueden sobrescribir si lo deseamos.Se utiliza un literal de cadena para crear estos bloques de caracteres en algún lugar de la memoria al que
s
apunta este puntero . Aquí podemos reasignar el objeto al que apunta cambiando eso, pero siempre que apunte a una cadena literal, el bloque de caracteres al que apunta no se puede cambiar.fuente
Además, considere que, para fines de solo lectura, el uso de ambos es idéntico, puede acceder a un carácter indexando con
[]
o con*(<var> + <index>)
formato:Y:
Obviamente, si intentas hacer
Probablemente obtendrá un error de segmentación, ya que está intentando acceder a la memoria de solo lectura.
fuente
x[1] = 'a';
lo que segfault también (dependiendo de la plataforma, por supuesto).Solo para agregar: también obtienes diferentes valores para sus tamaños.
Como se mencionó anteriormente, para una matriz
'\0'
se asignará como el elemento final.fuente
Lo anterior establece que str apunte al valor literal "Hola", que está codificado en la imagen binaria del programa, que está marcado como de solo lectura en la memoria, significa que cualquier cambio en este literal de cadena es ilegal y arrojaría fallas de segmentación.
copia la cadena a la memoria recién asignada en la pila. Por lo tanto, cualquier cambio en él está permitido y es legal.
cambiará el str a "Mello".
Para obtener más detalles, consulte la pregunta similar:
¿Por qué aparece un error de segmentación al escribir en una cadena inicializada con "char * s" pero no "char s []"?
fuente
En el caso de:
x es un valor l , se le puede asignar. Pero en el caso de:
x no es un valor l, es un valor r, no se le puede asignar.
fuente
x
es un valor no modificable. Sin embargo, en casi todos los contextos, evaluará a un puntero a su primer elemento, y eso valor es un valor.fuente
A la luz de los comentarios aquí, debería ser obvio que: char * s = "hola"; Es una mala idea, y debe usarse en un ámbito muy limitado.
Esta podría ser una buena oportunidad para señalar que la "corrección constante" es una "cosa buena". Siempre que pueda, use la palabra clave "const" para proteger su código, de las personas que llaman o los programadores "relajados", que generalmente son más "relajados" cuando entran en juego los punteros.
Suficiente melodrama, esto es lo que uno puede lograr al adornar punteros con "const". (Nota: hay que leer las declaraciones de puntero de derecha a izquierda). Estas son las 3 formas diferentes de protegerse cuando se juega con punteros:
- es decir, el objeto DBJ no se puede cambiar a través de p.
- es decir, puede cambiar el objeto DBJ a través de p, pero no puede cambiar el puntero p mismo.
- es decir, no puede cambiar el puntero p, ni puede cambiar el objeto DBJ a través de p.
Los errores relacionados con los intentos de mutaciones const-ant se detectan en tiempo de compilación. No hay tiempo de ejecución ni penalización de velocidad para const.
(Por supuesto, ¿está utilizando el compilador de C ++?)
--DBJ
fuente