Un nom de tableau est-il un pointeur?

203

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?

Lundin
la source
4
Non. Mais le tableau est le même & tableau [0]
36
@pst: &array[0]donne un pointeur, pas un tableau;)
jalf
28
@Nava (et pst): array et & array [0] ne sont pas vraiment les mêmes. Exemple : sizeof (array) et sizeof (& array [0]) donnent des résultats différents.
Thomas Padron-McCarthy
1
@Thomas est d'accord, mais en termes de pointeurs, lorsque vous déréférencez tableau et & tableau [0], ils produisent la même valeur de tableau [0] .ie * tableau == tableau [0]. Personne ne voulait dire que ces deux pointeurs sont identiques, mais dans ce cas spécifique (pointant vers le premier élément), vous pouvez utiliser le nom du tableau soit.
Nava Carmon
1
Ceux-ci pourraient également vous aider à mieux comprendre: stackoverflow.com/questions/381542 , stackoverflow.com/questions/660752
Dinah

Réponses:

255

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;

pne 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 adans le pointeur p(quoi que cela signifie). Au lieu de cela, le nom du tableau aest 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 pde 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 adans 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' sizeofopérateur sur un tableau. Si a aété converti en pointeur dans ce contexte, sizeof adonnerait la taille d'un pointeur et non du tableau réel, ce qui serait plutôt inutile, donc dans ce cas asignifie le tableau lui-même.

Thomas Padron-McCarthy
la source
5
Une conversion automatique similaire est appliquée aux pointeurs de fonction - les deux functionpointer()et (*functionpointer)()signifient la même chose, étrangement.
Carl Norum
3
Il n'a pas demandé si les tableaux et les pointeurs sont identiques, mais si le nom d'un tableau est un pointeur
Ricardo Amores
32
Un nom de tableau n'est pas un pointeur. C'est un identifiant pour une variable de type tableau, qui a une conversion implicite en pointeur de type élément.
Pavel Minaev,
29
De plus, à part 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, &asera 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.
Pavel Minaev,
3
@ onmyway133, consultez ici pour une brève explication et d'autres citations.
Carl Norum
37

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 */
pmg
la source
20

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 avous donne la taille (en octets) de l'ensemble du tableau. L'expression &avous donne l'adresse du tableau, qui est la même que l'adresse du premier élément . La différence entre &aet &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 an'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 sizeofou 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 *aa 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 getsfonction é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é.


  1. Lequel * peut * affecter la façon dont la valeur d'adresse est interprétée - dépend de la machine.
John Bode
la source
Je cherche depuis assez longtemps cette réponse. Je vous remercie! Et si vous le savez, pourriez-vous dire un peu plus ce qu'est une expression de tableau. 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)?
Andriy Dmytruk
Et encore une chose. Un tableau de longueur 5 est de type int [5]. C'est donc d'où nous connaissons la longueur lorsque nous appelons sizeof (array) - à partir de son type? Et cela signifie que les tableaux de longueur différente sont comme différents types de constantes?
Andriy Dmytruk
@AndriyDmytruk: sizeofest 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 intlargeur de 4 octets, un tableau de 5 éléments intoccupe 20 octets.
John Bode
L'opérateur n'est-il pas [ ]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 an'est pas converti en type de pointeur int*(bien que s'il as'agit d'un argument d'une fonction, il doit être converti int*).
Stan
2
@Stan: L'expression aa 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. apointe vers le premier tableau à 3 éléments de int, a + 1pointe vers le deuxième tableau à 3 éléments de int, *(a + 1) est le deuxième tableau à 3 éléments de int, *(a + 1) + 2pointe 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.
John Bode
5

Un tableau déclaré comme ceci

int a[10];

alloue de la mémoire pendant 10 ints. Vous ne pouvez pas modifier amais 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 intart. 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
Grumdrig
la source
2
Les tableaux ne sont pas toujours alloués sur la pile. C'est un détail d'implémentation qui variera d'un compilateur à l'autre. Dans la plupart des cas, les tableaux statiques ou globaux seront alloués à partir d'une région de mémoire différente de la pile. Des tableaux de types const peuvent être alloués à partir d'une autre région de mémoire
Mark Bessey
1
Je pense que Grumdrig voulait dire "alloue 10 ints avec une durée de stockage automatique".
Courses de légèreté en orbite
4

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]);
Michael Buen
la source
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 cet a"pointer" vers la même mémoire, vous pouvez obtenir l'adresse du cpointeur, mais vous ne pouvez pas obtenir l'adresse du apointeur.

Palo
la source
1
"Il compile très bien (avec 2 avertissements)". Ce n'est pas bien. Si vous dites à gcc de le compiler en C standard approprié en ajoutant -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.
Lundin
Merci Lundin pour ton commentaire. Vous savez, il existe de nombreuses normes. J'ai essayé de clarifier ce que je voulais dire dans le montage. Le int **type n'est pas le point là, il vaut mieux utiliser le void *pour cela.
Palo
-3

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
Amitesh Ranjan
la source
-4

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».

Ricardo Amores
la source
1
Un nom de tableau n'est pas identique à un pointeur const. Étant donné: int a [10]; int * p = a; sizeof (p) et sizeof (a) ne sont pas les mêmes.
Mark Bessey
1
Il existe d'autres différences. En général, il est préférable de s'en tenir à la terminologie utilisée par la norme C, qui l'appelle spécifiquement une «conversion». Quote: "Sauf s'il s'agit de l'opérande de l'opérateur sizeof ou de l'opérateur unaire &, ou s'il s'agit d'un littéral de chaîne utilisé pour initialiser un tableau, une expression de type" "tableau de type" "est convertie en une expression de type" 'pointer to type' 'qui pointe vers l'élément initial de l'objet tableau et n'est pas une valeur l. Si l'objet tableau a une classe de stockage de registre, le comportement n'est pas défini. "
Pavel Minaev,
-5

Le nom du tableau est l'adresse du 1er élément d'un tableau. Donc, oui, le nom du tableau est un pointeur const.

SAQIB SOHAIL BHATTI
la source