Comment se fait-il que l'adresse d'un tableau soit égale à sa valeur en C?

189

Dans le bit de code suivant, les valeurs de pointeur et les adresses de pointeur diffèrent comme prévu.

Mais les valeurs et adresses de tableau ne le font pas!

Comment se peut-il?

Production

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
la source
De la FAQ comp.lang.c: - [Alors que signifie `` l'équivalence des pointeurs et des tableaux '' en C? ] ( c-faq.com/aryptr/aryptrequiv.html ) - [Puisque les références de tableau se désintègrent en pointeurs, si arr est un tableau, quelle est la différence entre arr et & arr? ] ( c-faq.com/aryptr/aryvsadr.html ) Ou allez lire toute la section Arrays and Pointers .
jamesdlin
3
J'avais ajouté une réponse avec un diagramme à cette question il y a deux ans. Qu'est-ce qui sizeof(&array)revient?
Grijesh Chauhan

Réponses:

214

Le nom d'un tableau s'évalue généralement à l'adresse du premier élément du tableau, donc arrayet &arraya la même valeur (mais des types différents, donc array+1et ne&array+1 sera pas égal si le tableau a plus d'un élément de long).

Il existe deux exceptions à cela: lorsque le nom du tableau est un opérande de sizeofou unaire &(adresse de), le nom fait référence à l'objet tableau lui-même. Ainsi, sizeof arrayvous donne la taille en octets du tableau entier, pas la taille d'un pointeur.

Pour un tableau défini comme T array[size], il aura le type T *. Quand / si vous l'incrémentez, vous accédez à l'élément suivant du tableau.

&arrayévalue à la même adresse, mais étant donné la même définition, il crée un pointeur du type T(*)[size]- c'est-à-dire qu'il s'agit d'un pointeur vers un tableau, pas vers un seul élément. Si vous incrémentez ce pointeur, il ajoutera la taille du tableau entier, et non la taille d'un seul élément. Par exemple, avec un code comme celui-ci:

char array[16];
printf("%p\t%p", (void*)&array, (void*)(&array+1));

Nous pouvons nous attendre à ce que le deuxième pointeur soit 16 plus grand que le premier (car c'est un tableau de 16 caractères). Puisque% p convertit généralement les pointeurs en hexadécimal, cela pourrait ressembler à quelque chose comme:

0x12341000    0x12341010
Jerry Coffin
la source
3
@Alexandre: &arrayest un pointeur vers le premier élément du tableau, où as se arrayréfère au tableau entier. La différence fondamentale peut également être observée en comparant sizeof(array), à sizeof(&array). Notez cependant que si vous passez arrayen argument à une fonction, seul &arrayest en fait passé. Vous ne pouvez pas transmettre un tableau par valeur à moins qu'il ne soit encapsulé avec un struct.
Clifford
14
@Clifford: Si vous passez un tableau à une fonction, il se désintègre en un pointeur vers son premier élément si efficacement &array[0]passé, pas &arrayce qui serait un pointeur vers le tableau. C'est peut-être un petit choix, mais je pense qu'il est important de clarifier; les compilateurs avertiront si la fonction a un prototype qui correspond au type du pointeur transmis.
CB Bailey
2
@Jerry Coffin Par exemple int * p = & a, si je veux l'adresse mémoire du pointeur int p, je peux faire & p. Puisque & array se convertit en l'adresse du tableau entier (qui commence à l'adresse du premier élément). Alors comment trouver l'adresse mémoire du pointeur de tableau (qui stocke l'adresse du premier élément du tableau)? Ça doit être quelque part dans la mémoire, non?
John Lee
2
@JohnLee: Non, il n'est pas nécessaire qu'il y ait un pointeur vers le tableau n'importe où dans la mémoire. Si vous créez un pointeur, vous pouvez alors prendre son adresse: int *p = array; int **pp = &p;.
Jerry Coffin
3
@Clifford le premier commentaire est faux, pourquoi le garder? Je pense que cela pourrait conduire à un malentendu pour ceux qui ne lisent pas la réponse (@Charles) suivante.
Rick
30

C'est parce que le nom du tableau ( my_array) est différent d'un pointeur vers un tableau. C'est un alias de l'adresse d'un tableau et son adresse est définie comme l'adresse du tableau lui-même.

Le pointeur est une variable C normale sur la pile, cependant. Ainsi, vous pouvez prendre son adresse et obtenir une valeur différente de l'adresse qu'elle contient.

J'ai écrit sur ce sujet ici - s'il vous plaît jeter un oeil.

Eli Bendersky
la source
& My_array ne devrait-il pas être une opération non valide puisque la valeur de my_array n'est pas sur la pile, seuls my_array [0 ... length] le sont? Alors tout aurait du sens ...
Alexandre
@Alexandre: Je ne sais pas pourquoi c'est autorisé, en fait.
Eli Bendersky
Vous pouvez prendre l'adresse de n'importe quelle variable (si non marquée register) quelle que soit sa durée de stockage: statique, dynamique ou automatique.
CB Bailey
my_arraylui-même est sur la pile, car my_array c'est le tableau entier.
caf
3
my_array, lorsqu'il n'est pas le sujet des opérateurs &ou sizeof, est évalué comme un pointeur vers son premier élément (c'est-à-dire &my_array[0]) - mais my_arraylui-même n'est pas ce pointeur ( my_arrayest toujours le tableau). Ce pointeur est juste une rvaleur éphémère (par exemple donnée int a;, c'est juste comme a + 1) - conceptuellement au moins, il est "calculé selon les besoins". La vraie "valeur" de my_arrayest le contenu de l'ensemble du tableau - c'est juste qu'épingler cette valeur en C est comme essayer d'attraper du brouillard dans un bocal.
caf
28

En C, lorsque vous utilisez le nom d'un tableau dans une expression (y compris en le passant à une fonction), à moins qu'il ne s'agisse de l'opérande de l' &opérateur address-of ( ) ou de l' sizeofopérateur, il se décompose en un pointeur vers son premier élément.

Autrement dit, dans la plupart des contextes, arrayéquivaut à la &array[0]fois au type et à la valeur.

Dans votre exemple, my_arraya un type char[100]qui se désintègre en a char*lorsque vous le passez à printf.

&my_arraya type char (*)[100](pointeur vers un tableau de 100 char). Comme il s'agit de l'opérande to &, c'est l'un des cas qui my_arrayne se désintègre pas immédiatement en un pointeur vers son premier élément.

Le pointeur vers le tableau a la même valeur d'adresse qu'un pointeur vers le premier élément du tableau, car un objet tableau est juste une séquence contiguë de ses éléments, mais un pointeur vers un tableau a un type différent vers un pointeur vers un élément de ce tableau. Ceci est important lorsque vous effectuez une arithmétique de pointeur sur les deux types de pointeur.

pointer_to_arraya type char *- initialisé pour pointer sur le premier élément du tableau car c'est ce qui my_arrayse désintègre dans l'expression d'initialisation - et &pointer_to_array a type char **(pointeur vers un pointeur vers a char).

Parmi ceux-ci: my_array(après décroissance vers char*), &my_arrayet pointer_to_arraytous pointent directement sur le tableau ou le premier élément du tableau et ont donc la même valeur d'adresse.

CB Bailey
la source
3

La raison pour laquelle my_arrayet le &my_arrayrésultat dans la même adresse peuvent être facilement compris lorsque vous regardez la disposition de la mémoire d'un tableau.

Disons que vous avez un tableau de 10 caractères (au lieu des 100 dans votre code).

char my_array[10];

La mémoire pour my_arrayressemble à quelque chose comme:

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

En C / C ++, un tableau se décompose en pointeur vers le premier élément d'une expression telle que

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

Si vous examinez où se trouve le premier élément du tableau, vous verrez que son adresse est la même que l'adresse du tableau:

my_array[0]
|
v
+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array[0].
R Sahu
la source
3

Dans le langage de programmation B, qui était le prédécesseur immédiat de C, les pointeurs et les entiers étaient librement interchangeables. Le système se comporterait comme si toute la mémoire était un tableau géant. Chaque nom de variable était associé à une adresse globale ou relative à la pile, pour chaque nom de variable, les seules choses dont le compilateur devait garder une trace était de savoir s'il s'agissait d'une variable globale ou locale, et son adresse par rapport à la première variable globale ou locale. variable.

Étant donné une déclaration mondiale comme i;[il n'y avait pas besoin de spécifier un type, puisque tout était un entier / pointeur] serait traité par le compilateur: address_of_i = next_global++; memory[address_of_i] = 0;et une déclaration comme i++serait traitée comme: memory[address_of_i] = memory[address_of_i]+1;.

Une déclaration comme arr[10];serait traitée comme address_of_arr = next_global; memory[next_global] = next_global; next_global += 10;. Notez que dès que cette déclaration était traitée, le compilateur pouvait immédiatement oublier d' arrêtre un tableau . Une instruction comme arr[i]=6;serait traitée comme memory[memory[address_of_a] + memory[address_of_i]] = 6;. Le compilateur ne se soucierait pas de savoir s'il arrreprésentait un tableau et iun entier, ou vice versa. En effet, cela ne se soucierait pas de savoir s'il s'agissait des deux tableaux ou des deux entiers; il générerait parfaitement le code tel que décrit, sans se soucier de savoir si le comportement résultant serait probablement utile.

L'un des objectifs du langage de programmation C était d'être largement compatible avec B. En B, le nom d'un tableau [appelé "vecteur" dans la terminologie de B] identifiait une variable contenant un pointeur qui était initialement assigné pour pointer vers au premier élément d'une allocation de la taille donnée, donc si ce nom apparaissait dans la liste d'arguments d'une fonction, la fonction recevrait un pointeur vers le vecteur. Même si C a ajouté des types de tableaux «réels», dont le nom était rigidement associé à l'adresse de l'allocation plutôt qu'à une variable de pointeur qui pointerait initialement vers l'allocation, la décomposition des tableaux en pointeurs a fait que le code déclarant un tableau de type C se comporte de la même manière en code B qui a déclaré un vecteur et n'a jamais modifié la variable contenant son adresse.

supercat
la source
1

En fait &myarray, les myarraydeux sont l'adresse de base.

Si vous voulez voir la différence au lieu d'utiliser

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

utilisation

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