Dans le bit de code suivant, les valeurs et les adresses de pointeur diffèrent comme prévu.
Mais les valeurs de tableau et les adresses ne le font pas!
Comment se peut-il?
Sortie
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;
}
Le nom d'un tableau correspond généralement à l'adresse du premier élément du tableau. Ainsi, array
et &array
Ont la même valeur (mais des types différents, donc array+1
Et &array+1
Sera pas égal si le tableau est plus long que 1 élément).
Il existe deux exceptions à cela: lorsque le nom du tableau est un opérande de sizeof
ou unaire &
(Address-of), le nom fait référence à l'objet tableau lui-même. Ainsi sizeof array
Vous donne la taille en octets de tout le tableau, 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 passez à l'élément suivant du tableau.
&array
Correspond à la même adresse, mais avec la même définition, il crée un pointeur de type T(*)[size]
- c'est-à-dire qu'il s'agit d'un pointeur sur un tableau et non sur un seul élément. Si vous incrémentez ce pointeur, la taille de l'ensemble du tableau sera ajoutée, pas 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 (parce que c'est un tableau de 16 caractères). Puisque% p convertit généralement les pointeurs en hexadécimal, cela pourrait ressembler à ceci:
0x12341000 0x12341010
C'est parce que le nom du tableau (my_array
) est différent d’un pointeur sur un tableau. C'est un alias pour l'adresse d'un tableau, et son adresse est définie comme l'adresse du tableau lui-même.
Le pointeur est cependant une variable C normale sur la pile. Ainsi, vous pouvez prendre son adresse et obtenir une valeur différente de l'adresse qu'il contient.
J'ai écrit sur ce sujet ici - jetez-y un coup d'œil.
En C, lorsque vous utilisez le nom d'un tableau dans une expression (y compris en le transmettant à une fonction), sauf s'il s'agit de l'opérande de l'opérateur address-of (&
) Ou de sizeof
. opérateur, il décroît à un pointeur sur son premier élément.
Autrement dit, dans la plupart des contextes, array
équivaut à &array[0]
En type et en valeur.
Dans votre exemple, my_array
A le type char[100]
Qui se décompose en un char*
Lorsque vous le transmettez à printf.
&my_array
Est de type char (*)[100]
(pointeur sur un tableau de 100 char
). Comme il s'agit de l'opérande de &
, Il s'agit d'un des cas où my_array
Ne se désintègre pas immédiatement en un pointeur vers son premier élément.
Le pointeur sur le tableau a la même valeur d'adresse qu'un pointeur sur le premier élément du tableau car un objet tableau est simplement une séquence contiguë de ses éléments, mais un pointeur sur un tableau a un type différent du pointeur sur 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_array
A le type char *
- initialisé pour pointer sur le premier élément du tableau, car c'est ce à quoi my_array
Se désintègre dans l'expression de l'initialiseur - et &pointer_to_array
tapez char **
(pointeur sur un pointeur sur un char
).
Parmi ceux-ci: my_array
(Après décroissance en char*
), &my_array
Et pointer_to_array
Pointent tous directement vers le tableau ou le premier élément du tableau, etc. avoir la même valeur d'adresse.
Dans le langage de programmation B, 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 est associé à une adresse globale ou relative à la pile. Pour chaque nom de variable, le compilateur devait simplement savoir si c’était une variable globale ou locale et son adresse par rapport à la première variable globale ou locale. variable.
Étant donné une déclaration globale comme i;
[il n’était pas nécessaire de spécifier un type, car tout était un entier/un pointeur] serait traité par le compilateur comme suit: address_of_i = next_global++; memory[address_of_i] = 0;
et une déclaration comme i++
_ serait traité comme: memory[address_of_i] = memory[address_of_i]+1;
.
Une déclaration comme arr[10];
serait traité comme address_of_arr = next_global; memory[next_global] = next_global; next_global += 10;
. Notez que dès que cette déclaration a été traitée, le compilateur peut immédiatement oublier que arr
est un tablea. Une déclaration comme arr[i]=6;
serait traité comme memory[memory[address_of_a] + memory[address_of_i]] = 6;
. Le compilateur ne se soucierait pas de savoir si arr
représentait un tableau et i
un entier, ou inversement. En effet, il serait indifférent qu’ils soient à la fois des tableaux ou des 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 devait ê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 affecté à pointer vers au premier élément d’une allocation de la taille donnée. Ainsi, si ce nom apparaît dans la liste des arguments d’une fonction, celle-ci recevra un pointeur sur le vecteur. Même si C a ajouté des types de tableaux "réels", dont le nom était associé de manière rigide à l'adresse de l'allocation plutôt qu'à une variable de pointeur qui pointerait initialement sur l'allocation, les tableaux se décomposant en pointeurs avec du code déclarant un tableau de type C se comporter de manière identique. au code B qui a déclaré un vecteur puis n’a jamais modifié la variable contenant son adresse.
La raison pour laquelle my_array
et &my_array
résultat dans la même adresse peut ê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 de 100 dans votre code).
char my_array[10];
Mémoire pour my_array
ressemble à 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 le premier élément du tableau, vous verrez que son adresse est la même que celle du tableau:
my_array[0]
|
v
+---+---+---+---+---+---+---+---+---+---+
| | | | | | | | | | |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array[0].
Réellement &myarray
et myarray
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);