Le nom d'un tableau est-il un pointeur en C? Sinon, quelle est la différence entre le nom d'un tableau et une variable de pointeur?
Un tableau est un tableau et un pointeur est un pointeur, mais dans la plupart des cas, les noms de tableau sont convertis en pointeurs. Un terme souvent utilisé est qu'ils décroissance à des pointeurs.
Voici un tableau:
int a[7];
a
contient un espace pour sept entiers, et vous pouvez mettre une valeur dans l'un d'eux avec une affectation, comme ceci:
a[3] = 9;
Voici un pointeur:
int *p;
p
ne contient aucun espace pour les nombres entiers, mais il peut pointer sur un espace pour un nombre entier. Nous pouvons, par exemple, le définir pour qu'il pointe vers l'un des emplacements du tableau a
, tel que le premier:
p = &a[0];
Ce qui peut être déroutant, c'est que vous pouvez aussi écrire ceci:
p = a;
Ceci not copie le contenu du tableau a
dans le pointeur p
(quoi que cela veuille dire). Au lieu de cela, le nom du tableau a
est converti en un pointeur sur son premier élément. Donc, cette affectation fait la même chose que la précédente.
Vous pouvez maintenant utiliser p
de la même manière qu'un tableau:
p[3] = 17;
La raison pour laquelle cela fonctionne est que l'opérateur de déréférencement de tableau en C, [ ]
, Est défini en termes de pointeurs. x[y]
Signifie: commencez par le pointeur x
, étape y
éléments après le pointeur, puis prenez ce qui est là. En utilisant la syntaxe arithmétique du pointeur, x[y]
Peut également être écrit sous la forme *(x+y)
.
Pour que cela fonctionne avec un tableau normal, tel que notre a
, le nom a
dans a[3]
Doit d'abord être converti en un pointeur (au premier élément de a
). Ensuite, nous faisons avancer 3 éléments et prenons ce qui est là. En d'autres termes: prenez l'élément en position 3 dans le tableau. (Quel est le quatrième élément du tableau, puisque le premier est numéroté 0)
Ainsi, en résumé, les noms de tableaux dans un programme C sont (dans la plupart des cas) convertis en pointeurs. Une exception est lorsque nous utilisons l'opérateur sizeof
sur un tableau. Si a
était converti en pointeur dans ce contexte, sizeof a
Donnerait la taille d'un pointeur et non du tableau réel, ce qui serait plutôt inutile, donc dans ce cas a
_ signifie le tableau lui-même.
Lorsqu'un tableau est utilisé comme valeur, son nom représente l'adresse du premier élément.
Lorsqu'un tableau n'est pas utilisé en tant que valeur, son nom représente le tableau entier.
int arr[7];
/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */
/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */
Si une expression de type tableau (telle que le nom du tableau) apparaît dans une expression plus grande et qu'il ne s'agit pas de l'opérande des opérateurs &
Ou sizeof
, le type de l'expression du tableau est converti de "tableau de N-éléments de T" en "pointeur sur T", et la valeur de l'expression est l'adresse du premier élément du tableau.
En bref, le nom du tableau n'est pas un pointeur, mais dans la plupart des contextes, il est traité comme si il s'agissait d'un pointeur.
Éditer
Répondre à la question dans le commentaire:
Si j'utilise sizeof, est-ce que je ne compte que la taille des éléments du tableau? Ensuite, la "tête" du tableau occupe également de l'espace avec les informations sur la longueur et un pointeur (et cela signifie que cela prend plus d'espace qu'un pointeur normal)?
Lorsque vous créez un tableau, le seul espace alloué est celui des éléments eux-mêmes. aucun stockage n'est matérialisé pour un pointeur séparé ou des métadonnées. Donné
char a[10];
ce que vous avez en mémoire est
+---+
a: | | a[0]
+---+
| | a[1]
+---+
| | a[2]
+---+
...
+---+
| | a[9]
+---+
L'expression a
fait référence à l'ensemble du tableau, mais il n'y a pas d'objet a
séparé des éléments du tableau eux-mêmes. Ainsi, sizeof a
Vous donne la taille (en octets) du tableau entier. L'expression &a
Vous donne l'adresse du tableau qui est identique à l'adresse du premier élément . La différence entre &a
Et &a[0]
Correspond au type du résultat.1 - char (*)[10]
dans le premier cas et char *
dans le second.
Lorsque vous voulez accéder à des éléments individuels, l’expression a[i]
Est définie comme le résultat de *(a + i)
- à partir d’une valeur d’adresse a
, offset i
éléments ( pas octets ) à partir de cette adresse et déréférencer le résultat.
Le problème est que a
n'est ni un pointeur ni une adresse - c'est tout l'objet tableau. Ainsi, la règle en C que chaque fois que le compilateur voit une expression de type tableau (telle que a
, qui a le type char [10]
) et Cette expression n'est pas l'opérande des opérateurs sizeof
ou unaires &
, le type de cette expression est converti ("décomposition") en un type de pointeur (char *
), Et la valeur de l'expression est l'adresse du premier élément du tableau. Par conséquent, l'expression a
a le même type et la même valeur que l'expression &a[0]
(Et par extension, l'expression *a
A le même type et la même valeur que l'expression a[0]
).
C était dérivé d'un langage antérieur appelé B, et dans B a
était un objet pointeur distinct des éléments du tableau a[0]
, a[1]
, Etc. Ritchie voulait conserver la sémantique du tableau de B, mais il ne voulait pas gâcher le stockage de l'objet pointeur séparé. Alors il s'en est débarrassé. Au lieu de cela, le compilateur convertira les expressions de tableau en expressions de pointeur pendant la traduction, si nécessaire.
Rappelez-vous que j'ai dit que les tableaux ne stockaient aucune métadonnée à propos de leur taille. Dès que cette expression de tableau "se décompose" en un pointeur, tout ce que vous avez est un pointeur en un seul élément. Cet élément peut être le premier d'une séquence d'éléments ou un objet unique. Il n'y a aucun moyen de savoir basé sur le pointeur lui-même.
Lorsque vous transmettez une expression de tableau à une fonction, la fonction reçoit uniquement un pointeur sur le premier élément. Elle n'a aucune idée de la taille du tableau (c'est pourquoi la fonction gets
était une telle menace et retiré de la bibliothèque). Pour que la fonction connaisse le nombre d'éléments contenus dans le tableau, vous devez utiliser une valeur sentinelle (telle que le terminateur 0 dans les chaînes C) ou indiquer le nombre d'éléments en tant que paramètre distinct.
Un tableau déclaré comme ça
int a[10];
alloue de la mémoire pour 10 int
s. Vous ne pouvez pas modifier a
mais vous pouvez utiliser l'arithmétique de pointeur avec a
.
Un pointeur tel que celui-ci n'alloue de la mémoire que pour le pointeur p
:
int *p;
Il n'alloue pas de int
s. Vous pouvez le modifier:
p = a;
et utilisez des indices de tableau comme vous pouvez avec un:
p[2] = 5;
a[2] = 5; // same
*(p+2) = 5; // same effect
*(a+2) = 5; // same effect
Le nom du tableau lui-même donne un emplacement mémoire, vous pouvez donc traiter le nom du tableau comme un pointeur:
int a[7];
a[0] = 1976;
a[1] = 1984;
printf("memory location of a: %p", a);
printf("value at memory location %p is %d", a, *a);
Et d’autres astuces que vous pouvez faire pour pointer (par exemple, ajouter/soustraire un décalage), vous pouvez aussi faire un tableau:
printf("value at memory location %p is %d", a + 1, *(a + 1));
En termes de langage, si C n’expose pas le tableau sous la forme d’une sorte de "pointeur" (sur le plan pédagogique, il s’agit simplement d’un emplacement de mémoire. Il ne peut pas pointer vers emplacement arbitraire en mémoire, ni peut être contrôlé par le programmeur). Nous avons toujours besoin de coder ceci:
printf("value at memory location %p is %d", &a[1], a[1]);
Je pense que cet exemple apporte un éclairage sur la question:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
int **b = &a;
printf("a == &a: %d\n", a == b);
return 0;
}
Il compile bien (avec 2 avertissements) dans gcc 4.9.2, et affiche ce qui suit:
a == &a: 1
oops :-)
Donc, la conclusion est non, le tableau n'est pas un pointeur, il n'est pas stocké en mémoire (pas même en lecture seule) en tant que pointeur, même si cela semble être le cas, puisque vous pouvez obtenir son adresse avec l'opérateur & . Mais - oups - cet opérateur ne fonctionne pas :-)), de toute façon, vous avez été prévenu:
p.c: In function ‘main’:
pp.c:6:12: warning: initialization from incompatible pointer type
int **b = &a;
^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
printf("a == &a: %d\n", a == b);
C++ refuse de telles tentatives avec des erreurs au moment de la compilation.
Modifier:
C'est ce que je voulais démontrer:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
void *c = a;
void *b = &a;
void *d = &c;
printf("a == &a: %d\n", a == b);
printf("c == &c: %d\n", c == d);
return 0;
}
Même si c
et a
"pointent" sur la même mémoire, vous pouvez obtenir l'adresse du pointeur c
, mais vous ne pouvez pas obtenir l'adresse du a
pointeur.