¿Cómo es que la dirección de una matriz es igual a su valor en C?
En el siguiente fragmento de código, los valores y las direcciones del puntero difieren como se esperaba.
¡Pero los valores de matriz y las direcciones no!
¿Cómo puede ser esto?
Producción
my_array = 0022FF00
&my_array = 0022FF00
pointer_to_array = 0022FF00
&pointer_to_array = 0022FEFC
#include <stdio.h>
int main()
{
char my_array[100] = "some cool string";
printf("my_array = %p\n", my_array);
printf("&my_array = %p\n", &my_array);
char *pointer_to_array = my_array;
printf("pointer_to_array = %p\n", pointer_to_array);
printf("&pointer_to_array = %p\n", &pointer_to_array);
printf("Press ENTER to continue...\n");
getchar();
return 0;
}
El nombre de una matriz generalmente se evalúa como la dirección del primer elemento de la matriz, por lo que array
y &array
tiene el mismo valor (pero de diferentes tipos, por lo que array+1
y no&array+1
será igual si la matriz tiene más de 1 elemento de largo).
Hay dos excepciones a esto: cuando el nombre de la matriz es un operando de sizeof
o unario &
(dirección de), el nombre se refiere al objeto de la matriz en sí. Por lo tanto, sizeof array
le proporciona el tamaño en bytes de toda la matriz, no el tamaño de un puntero.
Para una matriz definida como T array[size]
, tendrá tipo T *
. Cuando/si lo incrementas, llegas al siguiente elemento de la matriz.
&array
evalúa la misma dirección, pero dada la misma definición, crea un puntero del tipo T(*)[size]
, es decir, es un puntero a una matriz, no a un solo elemento. Si incrementa este puntero, agregará el tamaño de toda la matriz, no el tamaño de un solo elemento. Por ejemplo, con código como este:
char array[16];
printf("%p\t%p", (void*)&array, (void*)(&array+1));
Podemos esperar que el segundo puntero sea 16 mayor que el primero (porque es una matriz de 16 caracteres). Dado que %p normalmente convierte punteros en hexadecimal, podría verse así:
0x12341000 0x12341010
Esto se debe a que el nombre de la matriz ( my_array
) es diferente de un puntero a la matriz. Es un alias de la dirección de una matriz y su dirección se define como la dirección de la propia matriz.
Sin embargo, el puntero es una variable C normal en la pila. Por lo tanto, puede tomar su dirección y obtener un valor diferente de la dirección que contiene.
Escribí sobre este tema aquí ; échale un vistazo.
En C, cuando se utiliza el nombre de una matriz en una expresión (incluido pasarlo a una función), a menos que sea el operando del &
operador de dirección de () o el sizeof
operador, se descompone en un puntero a su primer elemento.
Es decir, en la mayoría de los contextos array
es equivalente &array[0]
tanto en tipo como en valor.
En su ejemplo, my_array
tiene un tipo char[100]
que decae a char*
cuando lo pasa a printf.
&my_array
tiene tipo char (*)[100]
(puntero a una matriz de 100 char
). Como es el operando de &
, este es uno de los casos que my_array
no decae inmediatamente en un puntero a su primer elemento.
El puntero a la matriz tiene el mismo valor de dirección que un puntero al primer elemento de la matriz, ya que un objeto de matriz es solo una secuencia contigua de sus elementos, pero un puntero a una matriz tiene un tipo diferente a un puntero a un elemento de esa matriz. Esto es importante cuando haces aritmética de punteros en los dos tipos de punteros.
pointer_to_array
tiene tipo char *
- inicializado para apuntar al primer elemento de la matriz, ya que eso es lo que my_array
decae en la expresión del inicializador - y &pointer_to_array
tiene tipo char **
(puntero a un puntero a a char
).
De estos: my_array
(después de desintegrarse en char*
), &my_array
y pointer_to_array
todos apuntan directamente a la matriz o al primer elemento de la matriz y, por lo tanto, tienen el mismo valor de dirección.
El motivo my_array
y &my_array
el resultado en la misma dirección se pueden entender fácilmente cuando se observa el diseño de la memoria de una matriz.
Digamos que tiene una matriz de 10 caracteres (en lugar de los 100 en su código).
char my_array[10];
La memoria para my_array
se parece a:
+---+---+---+---+---+---+---+---+---+---+
| | | | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array.
En C/C++, una matriz decae hasta el puntero al primer elemento en una expresión como
printf("my_array = %p\n", my_array);
Si examina dónde se encuentra el primer elemento de la matriz, verá que su dirección es la misma que la dirección de la matriz:
my_array[0]
|
v
+---+---+---+---+---+---+---+---+---+---+
| | | | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array[0].
En el lenguaje de programación B, que fue el predecesor inmediato de C, los punteros y los números enteros eran libremente intercambiables. El sistema se comportaría como si toda la memoria fuera una matriz gigante. Cada nombre de variable tenía asociada una dirección global o relativa a la pila; para cada nombre de variable, lo único que el compilador tenía que realizar un seguimiento era si era una variable global o local, y su dirección relativa a la primera variable global o local. variable.
Dada una declaración global como i;
[no había necesidad de especificar un tipo, ya que todo era un número entero/puntero] sería procesada por el compilador como: address_of_i = next_global++; memory[address_of_i] = 0;
y una declaración como i++
sería procesada como: memory[address_of_i] = memory[address_of_i]+1;
.
Una declaración como arr[10];
se procesaría como address_of_arr = next_global; memory[next_global] = next_global; next_global += 10;
. Tenga en cuenta que tan pronto como se procesó esa declaración, el compilador podría olvidarse inmediatamente de arr
ser una matriz . Una declaración como arr[i]=6;
se procesaría como memory[memory[address_of_a] + memory[address_of_i]] = 6;
. Al compilador no le importaría si arr
representa una matriz y i
un número entero, o viceversa. De hecho, no importaría si ambos fueran matrices o ambos números enteros; generaría perfectamente el código como se describe, sin tener en cuenta si el comportamiento resultante probablemente sería útil.
Uno de los objetivos del lenguaje de programación C era ser en gran medida compatible con B. En B, el nombre de una matriz [llamada "vector" en la terminología de B] identificaba una variable que contenía un puntero que inicialmente se asignó para apuntar a al primer elemento de una asignación del tamaño dado, por lo que si ese nombre apareciera en la lista de argumentos de una función, la función recibiría un puntero al vector. A pesar de que C agregó tipos de matrices "reales", cuyo nombre estaba rígidamente asociado con la dirección de la asignación en lugar de una variable de puntero que inicialmente apuntaría a la asignación, al descomponer las matrices en punteros, el código que declaraba una matriz de tipo C se comportaba de manera idéntica. al código B que declaró un vector y luego nunca modificó la variable que contiene su dirección.