¿Cómo es que la dirección de una matriz es igual a su valor en C?

189

En el siguiente bit de código, los valores de puntero y las direcciones de puntero difieren según lo esperado.

¡Pero los valores de matriz y las direcciones no!

¿Cómo puede ser esto?

Salida

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;
}
Alexandre
fuente
De las preguntas frecuentes de comp.lang.c: - [Entonces, ¿qué se entiende por `` equivalencia de punteros y matrices '' en C? ] ( c-faq.com/aryptr/aryptrequiv.html ) - [Dado que las referencias de matriz se descomponen en punteros, si arr es una matriz, ¿cuál es la diferencia entre arr y & arr? ] ( c-faq.com/aryptr/aryvsadr.html ) O vaya a leer toda la sección Arrays and Pointers .
jamesdlin
3
Agregué una respuesta con un diagrama a esta pregunta hace dos años aquí. ¿Qué sizeof(&array)devuelve?
Grijesh Chauhan

Respuestas:

214

El nombre de una matriz generalmente evalúa a la dirección del primer elemento de la matriz, de modo arrayy &arraytener el mismo valor (pero diferentes tipos, de modo array+1y &array+1será no ser igual si la matriz es más de 1 elemento de largo).

Hay dos excepciones a esto: cuando el nombre de la matriz es un operando sizeofo unario &(dirección de), el nombre se refiere al objeto de la matriz en sí. Por lo tanto, sizeof arrayle da 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 incrementa, llega al siguiente elemento en la matriz.

&arrayevalúa 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 un 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). Como% p generalmente convierte los punteros en hexadecimal, podría verse así:

0x12341000    0x12341010
Jerry Coffin
fuente
3
@Alexandre: &arrayes un puntero al primer elemento de la matriz, donde se arrayrefiere a toda la matriz. La diferencia fundamental también se puede observar al comparar sizeof(array), a sizeof(&array). Sin embargo, tenga en cuenta que si pasa arraycomo argumento a una función, solo &arrayse pasa de hecho. No puede pasar una matriz por valor a menos que esté encapsulada dentro de a struct.
Clifford
14
@Clifford: si pasa la matriz a una función, se desintegra a un puntero a su primer elemento, de modo que &array[0]se pasa de manera efectiva , lo &arrayque no sería un puntero a la matriz. Puede ser una trampa, pero creo que es importante dejar en claro; los compiladores avisarán si la función tiene un prototipo que coincida con el tipo de puntero pasado.
CB Bailey,
2
@Jerry Coffin Por ejemplo int * p = & a, si quiero la dirección de memoria del puntero int p, puedo hacer & p. Dado que & array se convierte en la dirección de toda la matriz (que comienza en la dirección del primer elemento). Entonces, ¿cómo encuentro la dirección de memoria del puntero de la matriz (que almacena la dirección del primer elemento en la matriz)? Debe estar en algún lugar de la memoria, ¿verdad?
John Lee
2
@ JohnLee: No, no tiene que haber un puntero a la matriz en ningún lugar de la memoria. Si crea un puntero, a continuación, puede tener su dirección: int *p = array; int **pp = &p;.
Jerry Coffin
3
@Clifford el primer comentario está mal, ¿por qué aún lo guardas? Creo que podría conducir a malentendidos para aquellos que no leen la siguiente respuesta (@Charles).
Rick
30

Esto se debe a que el nombre de la matriz ( my_array) es diferente de un puntero a una matriz. Es un alias de la dirección de una matriz, y su dirección se define como la dirección de la matriz misma.

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 en su interior.

Escribí sobre este tema aquí , por favor, eche un vistazo.

Eli Bendersky
fuente
¿No debería & my_array ser una operación no válida ya que el valor de my_array no está en la pila, solo my_array [0 ... length] sí? Entonces todo tendría sentido ...
Alexandre
@Alexandre: No estoy seguro de por qué está permitido, en realidad.
Eli Bendersky
Puede tomar la dirección de cualquier variable (si no está marcada register) sea cual sea su duración de almacenamiento: estática, dinámica o automática.
CB Bailey
my_arraysí mismo está en la pila, porque my_array es toda la matriz.
caf
3
my_array, cuando no es el sujeto de los operadores &o sizeof, se evalúa en un puntero a su primer elemento (es decir &my_array[0]), pero en my_arraysí mismo no es ese puntero ( my_arraysigue siendo la matriz). Ese puntero es solo un valor efímero (por ejemplo int a;, dado , es como a + 1) - conceptualmente al menos se "calcula según sea necesario". El verdadero "valor" de my_arrayes el contenido de toda la matriz; es solo que precisar este valor en C es como tratar de atrapar la niebla en un frasco.
caf
28

En C, cuando utiliza el nombre de una matriz en una expresión (incluido el paso a una función), a menos que sea el operando del &operador address-of ( ) o el sizeofoperador, se desintegra en un puntero a su primer elemento.

Es decir, en la mayoría de los contextos arrayes equivalente &array[0]tanto en tipo como en valor.

En su ejemplo, my_arraytiene un tipo char[100]que decae a a char*cuando lo pasa a printf.

&my_arraytiene tipo char (*)[100](puntero a la matriz de 100 char). Como es el operando &, este es uno de los casos que my_arrayno decae inmediatamente a 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 puntero en los dos tipos de puntero.

pointer_to_arraytiene tipo char *, inicializado para apuntar al primer elemento de la matriz, ya que eso es lo que my_arraydecae en la expresión inicializadora, y &pointer_to_array tiene tipo char **(puntero a puntero a a char).

De estos: my_array(después de decaer a char*), &my_arrayy pointer_to_arraytodos apuntan directamente a la matriz o al primer elemento de la matriz y, por lo tanto, tienen el mismo valor de dirección.

CB Bailey
fuente
3

El motivo my_arrayy el &my_arrayresultado de la misma dirección se pueden entender fácilmente cuando observa el diseño de 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 se my_arrayparece a algo como:

+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array.

En C / C ++, una matriz decae al 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].
R Sahu
fuente
3

En el lenguaje de programación B, que fue el predecesor inmediato de C, los punteros y los 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 hacer un seguimiento era si era una variable global o local, y su dirección relativa a la primera global o local. variable.

Dada una declaración global como i;[no había necesidad de especificar un tipo, ya que todo era un entero / puntero] sería procesado por el compilador como: address_of_i = next_global++; memory[address_of_i] = 0;y una declaración como i++serían procesados como: memory[address_of_i] = memory[address_of_i]+1;.

Una declaración como arr[10];se procesará 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 arrser una matriz . Una declaración como arr[i]=6;se procesará como memory[memory[address_of_a] + memory[address_of_i]] = 6;. Al compilador no le importaría si arrrepresentara una matriz y iun número entero, o viceversa. De hecho, no le importaría si ambos fueran matrices o ambos enteros; generaría perfectamente el código tal 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 ampliamente 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, de modo que si ese nombre apareciera en la lista de argumentos para una función, la función recibiría un puntero al vector. A pesar de que C agregó tipos de matriz "reales", cuyo nombre se asoció rígidamente con la dirección de la asignación en lugar de una variable de puntero que apunte inicialmente a la asignación, hacer que las matrices se descompongan en punteros hizo que el código declarara que una matriz de tipo C se comporta de manera idéntica al código B que declaró un vector y luego nunca modificó la variable que contiene su dirección.

Super gato
fuente
1

En realidad &myarrayymyarray ambas son la dirección base.

Si quieres ver la diferencia en lugar de usar

printf("my_array = %p\n", my_array);
printf("my_array = %p\n", &my_array);

utilizar

printf("my_array = %s\n", my_array);
printf("my_array = %p\n", my_array);
Ravi Bisla
fuente