Pourquoi l’adresse d’une variable tableau est-elle la même?

En C, si nous avons un tableau comme a[10] , alors a et &a ont la même valeur de pointeur (mais pas le même type). Je veux savoir pourquoi C a été conçu comme ça?

Était-ce pour économiser l’espace supplémentaire requirejs pour stocker &a ? … Cela a du sens quand on pense au fait qu’un ne peut jamais pointer vers un autre endroit, donc stocker &a n’a aucun sens.

le fait qu’un ne peut jamais pointer vers un autre endroit

Ce n’est pas un fait, cependant. Si a est un tableau, a ne pointe pas n’importe où car a n’est pas un pointeur. Étant donné int a[42]; , a nomme un tableau de 42 objects int ; ce n’est pas un pointeur sur un tableau de 42 objects int (ce serait int (*a)[42]; ).

&x vous donne l’adresse de l’object x ; si x est une variable de type tableau, alors &x vous donne l’adresse du tableau; si rien d’autre, cela est cohérent avec le comportement de & pour tout autre object.

Une meilleure question serait “pourquoi un tableau (comme a ) se désintègre-t-il en un pointeur vers son élément initial dans la plupart des cas lorsqu’il est utilisé?” Bien que je ne sache pas avec certitude pourquoi le langage a été conçu de cette façon, cela simplifie beaucoup la spécification de nombreuses choses, notamment, l’arithmétique avec un tableau est effectivement la même chose que l’arithmétique avec un pointeur.

Le design est assez élégant et plutôt nécessaire lorsque vous considérez comment faire référence à un tableau fonctionne au niveau de l’assemblage. Avec l’assemblage x86, considérez le code C suivant:

 void f(int array[]) { return; } void g(int (*array)[]) { return; } int main() { int a[5]; f(a); g(&a); return 0; } 

Le tableau a occupera 20 octets sur la stack puisqu’un int occupe généralement 4 octets sur la plupart des plates-formes. Avec le registre EBP pointant sur la base de l’enregistrement d’activation de la stack, vous examinerez l’assembly suivant pour la fonction main() ci-dessus:

 //subtract 20 bytes from the stack pointer register ESP for the array sub esp, 20 //the array is now allocated on the stack //get the address of the start of the array, and move it into EAX register lea eax, [ebp - 20] //push the address contained in EAX onto the stack for the call to f() //this is pretty much the only way that f() can refer to the array allocated //in the stack for main() push eax call f //clean-up the stack pop eax //get a pointer to the array of int's on the stack //(so the type is "int (*)[]") lea eax, [ebp - 20] //make the function call again using the stack for the function parameters push eax call g //...clean up the stack and return 

La commande d’assemblage LEA , ou “Load Effective Address”, calcule l’adresse à partir de l’expression de son deuxième opérande et la déplace dans le registre désigné par le premier opérande. Ainsi, chaque fois que nous appelons cette commande, cela ressemble à l’équivalent C de l’opérateur address-of. Vous remarquerez que l’adresse de démarrage du tableau (c.-à-d. [ebp - 20] ou 20 octets soustraits de la base de l’adresse du pointeur de stack située dans l’ EBP du ré-enregistrement) est toujours transmise à chacune des fonctions f et g . C’est à peu près la seule façon dont cela peut être fait au niveau du code machine pour faire référence à un bloc de mémoire alloué dans la stack d’une fonction dans une autre fonction sans avoir à copier réellement le contenu du tableau.

Ce qu’il faut retenir, c’est que les tableaux ne sont pas identiques aux pointeurs, mais qu’en même temps, le seul moyen efficace de faire référence à un tableau du côté droit de l’opérateur d’affectation ou de le transmettre à une fonction est de passer. it around by reference, ce qui signifie que se référer au tableau par nom est vraiment, au niveau de la machine, identique à l’obtention d’un pointeur sur le tableau. Par conséquent, au niveau du code machine, a , &a et même &a[0] dans ces situations sont dévolus dans le même ensemble d’instructions (dans cet exemple, lea eax, [ebp - 20] . Mais là encore, un type de tableau est pas un pointeur, et a , &a ne sont pas du même type, mais comme il désigne un bloc de mémoire, le moyen le plus simple et le plus efficace d’y faire référence est de le faire par un pointeur.

En fait, a[0] est en fait le même emplacement mémoire qu’un. &a représente l’adresse où a est stocké.

C’est différentes façons de représenter la même notation.

Aller à l’index 3 du tableau ( a[2] ) revient à faire a + sizeof( typeof(a) ) * 3typeof(a) est le type de la variable.

Oui

Votre explication est sur la bonne voie, bien que je ne sache pas si la quantité d’espace était en cause, mais plutôt le cas particulier de la nécessité de l’allouer du tout. Normalement, chaque object traité par C a une valeur (ou des valeurs) et une adresse. Ainsi, un pointeur réellement alloué a déjà une adresse et il est donc logique d’avoir à la fois une valeur et une adresse disponibles pour les pointeurs réels.

Mais une référence de tableau est déjà une adresse. Pour que C crée un pointeur double-indirect via l’opérateur &, il aurait fallu allouer de l’espace quelque part, ce qui aurait représenté une énorme divergence de philosophie pour le simple compilateur dmr C précédent.

La question de savoir où placer ce nouveau pointeur aurait été utile. Avec la même classe de stockage que le tableau? Et si c’était un paramètre? C’est la boîte de Pandore et le moyen le plus simple de le résoudre est de définir l’opération à distance. Si le développeur souhaite un pointeur indirect, il peut toujours en déclarer un.

De plus, il est judicieux de retourner l’adresse d’un object tableau, car cela correspond à son utilisation ailleurs.

Une bonne façon de voir cela est de voir que les objects ont des valeurs et des adresses et que la référence à un tableau n’est qu’une syntaxe abrégée. En fait, exiger &a aurait été un peu pédant, car la référence a n’aurait pas eu une autre interprétation de toute façon.

B est un ancêtre direct de C. C’était un langage non typé dans lequel la syntaxe

 tab[10]; 

avait plus ou moins le sens de

 Word tab_[10]; Word tab = (Word)&tab_; 

dans CIE, il a réservé 10 mots de mémoire et initialisé la variable avec l’adresse de la zone de mémoire.

Lorsque C évoluait, il était jugé utile de garder le fait qu’un tableau (BTW non seulement une variable de tableau, toute valeur de tableau, vous pouvez le voir avec des pointeurs et des tableaux multidimensionnels) se désintégrait en un pointeur vers son premier élément.

Manuel de B dans la page d’accueil de Dennis Ritchie qui contient d’autres informations historiques sur C et Unix.