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?
&array[0]
donne un pointeur, pas un tableau;)
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?
&array[0]
donne un pointeur, pas un tableau;)
Réponses:
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 se désintègrent aux pointeurs.
Voici un tableau:
int a[7];
a
contient de l'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 entiers, mais il peut pointer vers un espace pour un entier. Nous pouvons, par exemple, le définir pour pointer 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 également écrire ceci:
p = a;
Cela ne copie pas le contenu du tableau a
dans le pointeur p
(quoi que cela signifie). Au lieu de cela, le nom du tableau a
est converti en un pointeur sur son premier élément. Cette affectation fait donc la même chose que la précédente.
Vous pouvez maintenant utiliser p
de manière similaire à 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: commencer par le pointeur x
, avancer les y
éléments après ce vers quoi pointe le pointeur, puis prendre tout ce qui s'y trouve. L'utilisation de la syntaxe arithmétique du pointeur x[y]
peut également être écrite 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 (vers le premier élément dans a
). Ensuite, nous faisons avancer 3 éléments et prenons tout ce qui est là. En d'autres termes: prenez l'élément en position 3 dans le tableau. (Qui est le quatrième élément du tableau, car le premier est numéroté 0.)
Ainsi, en résumé, les noms de tableau dans un programme C sont (dans la plupart des cas) convertis en pointeurs. Une exception est lorsque nous utilisons l' sizeof
opérateur sur un tableau. Si a a
été 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.
functionpointer()
et (*functionpointer)()
signifient la même chose, étrangement.
sizeof()
, l'autre contexte dans lequel il n'y a pas de tableau -> la décroissance du pointeur est l'opérateur &
- dans votre exemple ci-dessus, &a
sera un pointeur vers un tableau de 7 int
, pas un pointeur vers un seul int
; c'est-à-dire que son type sera int(*)[7]
, qui n'est pas implicitement convertible en int*
. De cette façon, les fonctions peuvent réellement prendre des pointeurs vers des tableaux de taille spécifique et appliquer la restriction via le système de type.
Lorsqu'un tableau est utilisé comme valeur, son nom représente l'adresse du premier élément.
Lorsqu'un tableau n'est pas utilisé comme valeur, son nom représente l'ensemble du tableau.
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 (tel que le nom du tableau) apparaît dans une expression plus grande et que ce n'est pas l'opérande des opérateurs &
ou sizeof
, alors le type de l'expression du tableau est converti de "tableau à N éléments de T" en "pointeur vers 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 s'il s'agissait d'un pointeur.
Éditer
Répondre à la question dans le commentaire:
Si j'utilise sizeof, dois-je compter uniquement la taille des éléments du tableau? Ensuite, la «tête» du tableau prend également de l'espace avec les informations sur la longueur et un pointeur (et cela signifie qu'il prend plus d'espace qu'un pointeur normal)?
Lorsque vous créez un tableau, le seul espace alloué est l'espace pour les é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 obtenez 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) de l'ensemble du tableau. L'expression &a
vous donne l'adresse du tableau, qui est la même que l'adresse du premier élément . La différence entre &a
et &a[0]
est le type du résultat 1 - char (*)[10]
dans le premier cas et char *
dans le second.
Lorsque les choses deviennent bizarres, c'est lorsque vous souhaitez accéder à des éléments individuels - l'expression a[i]
est définie comme le résultat de *(a + i)
- étant donné une valeur d'adresse a
, des i
éléments de décalage (et non des octets ) de cette adresse et déréférencer le résultat.
Le problème est que ce a
n'est pas un pointeur ou une adresse - c'est tout l'objet tableau. Ainsi, la règle en C selon laquelle chaque fois que le compilateur voit une expression de type tableau (comme a
, qui a un type char [10]
) et que cette expression n'est pas l'opérande des opérateurs sizeof
ou unaires &
, le type de cette expression est converti ("désintégrations") à un pointeur type ( 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 est dérivé d'une langue antérieure appelée B, et B a
était un objet pointeur séparé des éléments du tableau a[0]
, a[1]
etc. Ritchie voulait garder la sémantique du tableau de B, mais il ne voulait pas salir avec stockage de l'objet pointeur séparé. Il s'en est donc débarrassé. Au lieu de cela, le compilateur convertira les expressions de tableau en expressions de pointeur pendant la traduction si nécessaire.
N'oubliez pas que j'ai dit que les tableaux ne stockent aucune métadonnée sur leur taille. Dès que cette expression de tableau "se désintègre" en un pointeur, tout ce que vous avez est un pointeur vers un seul élément. Cet élément peut être le premier d'une séquence d'éléments, ou il peut s'agir d'un seul objet. Il n'y a aucun moyen de savoir en fonction du pointeur lui-même.
Lorsque vous passez une expression de tableau à une fonction, tout ce que la fonction reçoit est un pointeur vers le premier élément - il n'a aucune idée de la taille du tableau (c'est pourquoi la gets
fonction était une telle menace et a finalement été supprimée de la bibliothèque). Pour que la fonction sache combien d'éléments le tableau possède, vous devez soit utiliser une valeur sentinelle (comme le terminateur 0 dans les chaînes C), soit passer le nombre d'éléments comme paramètre séparé.
sizeof
est un opérateur, et il évalue le nombre d' octets dans l'opérande (soit une expression désignant un objet, soit un nom de type entre parenthèses). Ainsi, pour un tableau, sizeof
évalue le nombre d'éléments multiplié par le nombre d'octets dans un seul élément. Si an a une int
largeur de 4 octets, un tableau de 5 éléments int
occupe 20 octets.
[ ]
spécial aussi? Par exemple, int a[2][3];
alors pour x = a[1][2];
, bien qu'il puisse être réécrit en tant que x = *( *(a+1) + 2 );
, ici a
n'est pas converti en type de pointeur int*
(bien que s'il a
s'agit d'un argument d'une fonction, il doit être converti int*
).
a
a le type int [2][3]
, qui "se désintègre" pour taper int (*)[3]
. L'expression *(a + 1)
a un type int [3]
qui "se désintègre" int *
. Ainsi, *(*(a + 1) + 2)
aura du type int
. a
pointe vers le premier tableau à 3 éléments de int
, a + 1
pointe vers le deuxième tableau à 3 éléments de int
, *(a + 1)
est le deuxième tableau à 3 éléments de int
, *(a + 1) + 2
pointe vers le troisième élément du deuxième tableau de int
, tout *(*(a + 1) + 2)
comme le troisième élément du deuxième tableau de int
. La façon dont cela est mappé au code machine dépend entièrement du compilateur.
Un tableau déclaré comme ceci
int a[10];
alloue de la mémoire pendant 10 int
s. Vous ne pouvez pas modifier a
mais vous pouvez faire de l'arithmétique de pointeur avec a
.
Un pointeur comme celui-ci alloue de la mémoire uniquement au pointeur p
:
int *p;
Il n'attribue aucun int
art. Vous pouvez le modifier:
p = a;
et utilisez les indices de tableau comme vous le pouvez avec:
p[2] = 5;
a[2] = 5; // same
*(p+2) = 5; // same effect
*(a+2) = 5; // same effect
int
s avec une durée de stockage automatique".
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 trucs astucieux que vous pouvez faire pour pointer (par exemple, ajouter / soustraire un décalage), vous pouvez également faire un tableau:
printf("value at memory location %p is %d", a + 1, *(a + 1));
Côté langage, si C n'a pas exposé le tableau comme une sorte de "pointeur" (pédantiquement, c'est juste un emplacement mémoire. Il ne peut pas pointer vers un emplacement arbitraire en mémoire, ni être contrôlé par le programmeur). Nous devons toujours coder ceci:
printf("value at memory location %p is %d", &a[1], a[1]);
Je pense que cet exemple met en lumière 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 très bien (avec 2 avertissements) dans gcc 4.9.2, et imprime ce qui suit:
a == &a: 1
Oups :-)
Donc, la conclusion est non, le tableau n'est pas un pointeur, il n'est pas stocké en mémoire (même pas en lecture seule) comme un pointeur, même s'il y ressemble, car vous pouvez obtenir son adresse avec l'opérateur & . Mais - oups - cet opérateur ne fonctionne pas :-)), de toute façon, vous avez été averti:
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.
Éditer:
Voici 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
"pointer" vers la même mémoire, vous pouvez obtenir l'adresse du c
pointeur, mais vous ne pouvez pas obtenir l'adresse du a
pointeur.
-std=c11 -pedantic-errors
, vous obtenez une erreur de compilation pour l'écriture de code C invalide. La raison en est que vous essayez d'affecter un int (*)[3]
à une variable de int**
, qui sont deux types qui n'ont absolument rien à voir l'un avec l'autre. Donc, ce que cet exemple est censé prouver, je n'en ai aucune idée.
int **
type n'est pas le point là, il vaut mieux utiliser le void *
pour cela.
Le nom du tableau se comporte comme un pointeur et pointe vers le premier élément du tableau. Exemple:
int a[]={1,2,3};
printf("%p\n",a); //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0
Les deux instructions d'impression donneront exactement la même sortie pour une machine. Dans mon système, cela donnait:
0x7fff6fe40bc0
Un tableau est une collection d'éléments sécuentiels et contigus en mémoire. En C, le nom d'un tableau est l'index du premier élément, et en appliquant un décalage, vous pouvez accéder au reste des éléments. Un "index du premier élément" est en effet un pointeur vers une direction mémoire.
La différence avec les variables de pointeur est que vous ne pouvez pas changer l'emplacement vers lequel pointe le nom du tableau, il est donc similaire à un pointeur const (il est similaire, pas le même. Voir le commentaire de Mark). Mais aussi que vous n'avez pas besoin de déréférencer le nom du tableau pour obtenir la valeur si vous utilisez l'arithmétique du pointeur:
char array = "hello wordl";
char* ptr = array;
char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'
La réponse est donc un peu «oui».
Le nom du tableau est l'adresse du 1er élément d'un tableau. Donc, oui, le nom du tableau est un pointeur const.