punteros - Literales de cadena frente a matriz de caracteres al inicializar un puntero
punteros y arreglos en c (4)
Creo que estás confundido porque
char *p = "ab";
y
char p[] = "ab";
tienen una semántica similar, pero diferentes significados.
Creo que el último caso (
char p[] = "ab";
) se considera mejor como una
notación abreviada
para
char p[] = {''a'', ''b'', ''/0''};
(Inicializa una matriz con el tamaño determinado por el inicializador).
En realidad, en este caso, se podría decir que
"ab"
no se usa realmente como un
literal de cadena
.
Sin embargo, el primer caso (
char *p = "ab";
) es diferente, ya que simplemente inicializa el puntero
p
para que apunte al primer elemento de la
cadena de solo lectura literal
"ab"
.
Espero que veas la diferencia.
Mientras
char p[] = "ab";
es representable como una inicialización como la que describió,
char *p = "ab";
no es, como los punteros, bueno, no son matrices, e inicializarlas con un inicializador de matrices hace algo completamente diferente (es decir, darles el valor del primer elemento,
0x61
en su caso).
Para resumir, los compiladores de C solo "reemplazan" un literal de cadena con un inicializador de matriz de caracteres si es adecuado para hacerlo, es decir, se está utilizando para inicializar una matriz de caracteres.
Inspirado por esta pregunta .
Podemos inicializar un puntero de caracteres mediante un literal de cadena:
char *p = "ab";
Y está perfectamente bien. Se podría pensar que es equivalente a lo siguiente:
char *p = {''a'', ''b'', ''/0''};
Pero aparentemente no es el caso.
Y no solo porque los literales de cadena se almacenan en una memoria de solo lectura, sino que parece que incluso a través del literal de cadena tiene un tipo de matriz de caracteres, y el inicializador
{...}
tiene el tipo de matriz de caracteres, dos declaraciones son manejado de manera diferente, ya que el compilador está dando la advertencia:
advertencia: exceso de elementos en inicializador escalar
En el segundo caso.
¿Cuál es la explicación de tal comportamiento?
Actualizar:
Además, en el último caso, el puntero
p
tendrá el valor de
0x61
(el valor del primer elemento de matriz
''a''
) en lugar de una ubicación de memoria, de modo que el compilador, como se advirtió, tome solo el primer elemento del inicializador y asignándolo a
p
.
Desde C99 tenemos
Un literal de cadena de caracteres es una secuencia de cero o más caracteres multibyte encerrados entre comillas dobles
Entonces, en la segunda definición, no hay una cadena literal ya que no está entre comillas dobles. Se debe asignar memoria al puntero antes de escribirle algo o, si desea ir por la lista de inicializadores, entonces
char p[] = {''a'',''b'',''/0''};
es lo que quieres Básicamente, ambas son declaraciones diferentes.
El segundo ejemplo es sintácticamente incorrecto.
En C,
{''a'', ''b'', ''/0''}
se puede usar para inicializar una matriz, pero no un puntero.
En su lugar, puede usar un literal compuesto C99 (también disponible en algunos compiladores como extensión, por ejemplo, GCC ) como este:
char *p = (char []){''a'', ''b'', ''/0''};
Tenga en cuenta que es más potente ya que el inicializador no necesariamente termina en nulo.
Los literales de cadena tienen un estado "mágico" en C. Son diferentes a cualquier otra cosa. Para entender por qué, es útil pensar en esto en términos de administración de memoria. Por ejemplo, pregúntese: "¿Dónde se almacena un literal de cadena en la memoria? ¿Cuándo se libera de la memoria?" y las cosas comenzarán a tener sentido.
Son diferentes a los literales numéricos que se traducen fácilmente en instrucciones de máquina. Para un ejemplo simplificado, algo como esto:
int x = 123;
... podría traducirse a algo como esto a nivel de máquina:
mov ecx, 123
Cuando hacemos algo como:
const char* str = "hello";
... ahora tenemos un dilema:
mov ecx, ???
No existe necesariamente una comprensión nativa del hardware de lo que realmente es una cadena de varios bytes y longitud variable. Principalmente sabe acerca de bits, bytes y números, y tiene registros diseñados para almacenar estas cosas, sin embargo, una cadena es un bloque de memoria que contiene varios de ellos.
Por lo tanto, los compiladores deben generar instrucciones para almacenar el bloque de memoria de esa cadena en algún lugar, y por lo general generan instrucciones al compilar su código para almacenar esa cadena en un lugar accesible a nivel mundial (generalmente un segmento de memoria de solo lectura o el segmento de datos).
También pueden fusionar múltiples cadenas literales que son idénticas para almacenarse en la misma región de memoria para evitar la redundancia.
Ahora puede generar una instrucción
mov/load
para cargar la dirección en la cadena literal, y luego puede trabajar con ella indirectamente a través de un puntero.
Otro escenario que podríamos encontrar es este:
static const char* some_global_ptr = "blah";
int main()
{
if (...)
{
const char* ptr = "hello";
...
some_global_ptr = ptr;
}
printf("%s/n", some_global_ptr);
}
Naturalmente,
ptr
fuera de alcance, pero necesitamos que la memoria de esa cadena literal permanezca para que este programa tenga un comportamiento bien definido.
Por lo tanto, las cadenas literales se traducen no solo en direcciones a bloques de memoria accesibles a nivel mundial, sino que tampoco se liberan siempre que su binario / programa esté cargado / ejecutándose para que no tenga que preocuparse por su administración de memoria.
[Editar: excluyendo optimizaciones potenciales: para el programador C, nunca tenemos que preocuparnos por la gestión de memoria de una cadena literal, por lo que el efecto es como si siempre estuviera ahí].
Ahora, sobre las matrices de caracteres, las cadenas literales no son necesariamente matrices de caracteres, per se.
En ningún punto del software podemos capturarlos en un valor r de matriz que nos pueda dar la cantidad de bytes asignados usando
sizeof
.
Solo podemos señalar la memoria a través de
char*/const char*
Este código en realidad nos da un identificador para dicha matriz sin involucrar un puntero:
char str[] = "hello";
Algo interesante sucede aquí. Es probable que un compilador de producción aplique todo tipo de optimizaciones, pero excluyendo esas, en un nivel básico, dicho código podría crear dos bloques de memoria separados.
El primer bloque será persistente durante la duración del programa y contendrá esa cadena literal,
"hello"
.
El segundo bloque será para esa matriz
str
real, y no es necesariamente persistente.
Si escribimos dicho código dentro de una función, asignará memoria en la pila, copiará esa cadena literal en la pila y liberará la memoria de la pila cuando
str
salga del alcance.
La dirección de
str
no va a coincidir con la cadena literal, por decirlo de otra manera.
Finalmente, cuando escribimos algo como esto:
char str[] = {''h'', ''e'', ''l'', ''l'', ''o'', ''/0''};
... no es necesariamente equivalente, ya que aquí no hay cadenas literales involucradas. Por supuesto, un optimizador puede hacer todo tipo de cosas, pero en este escenario, es posible que simplemente creemos un solo bloque de memoria (asignado en la pila y liberado de la pila si estamos dentro de una función) con instrucciones para mover todos estos números (caracteres) que especificó a la pila.
Entonces, si bien estamos logrando el mismo efecto que la versión anterior en lo que respecta a la lógica del software, en realidad estamos haciendo algo sutilmente diferente cuando no especificamos una cadena literal. Una vez más, los optimizadores pueden reconocer que hacer algo diferente puede tener el mismo efecto lógico, por lo que pueden ser elegantes aquí y hacer que estos dos sean efectivamente lo mismo en términos de instrucciones de la máquina. Pero aparte de eso, este es un código sutilmente diferente que estamos escribiendo.
Por último, pero no menos importante, cuando usamos inicializadores como {...}, el compilador espera que lo asigne a un valor l agregado con memoria que se asigna y se libera en algún momento cuando las cosas salen del alcance. Entonces es por eso que obtienes el error al intentar asignar tal cosa a un escalar (un solo puntero).