Pointeur C vers tableau / tableau de pointeurs désambiguïsation

463

Quelle est la différence entre les déclarations suivantes:

int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

Quelle est la règle générale pour comprendre des déclarations plus complexes?

George
la source
54
Voici un excellent article sur la lecture de déclarations complexes en C: unixwiz.net/techtips/reading-cdecl.html
jesper
@jesper Malheureusement, les qualificatifs constet volatile, qui sont à la fois importants et délicats, manquent dans cet article.
non-utilisateur
@ not-a-user ceux-ci ne sont pas pertinents pour cette question. Votre commentaire n'est pas pertinent. Veuillez vous abstenir.
user64742

Réponses:

439
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

Le troisième est le même que le premier.

La règle générale est la priorité des opérateurs . Cela peut devenir encore plus complexe lorsque des pointeurs de fonction apparaissent dans l'image.

Mehrdad Afshari
la source
4
Donc, pour les systèmes 32 bits: int * arr [8]; / * 8x4 octets alloués, pour chaque pointeur / int (* arr) [8]; / 4 octets alloués, seulement un pointeur * /
George
10
Nan. int * arr [8]: 8 x 4 octets alloués au total , 4 octets pour chaque pointeur. int (* arr) [8] est à droite, 4 octets.
Mehrdad Afshari,
2
J'aurais dû relire ce que j'ai écrit. Je voulais dire 4 pour chaque pointeur. Merci pour l'aide!
George
4
La raison pour laquelle la première est la même que la dernière est qu'elle est toujours autorisée à entourer les parenthèses autour des déclarants. P [N] est un déclarateur de tableau. P (....) est un déclarant de fonction et * P est un déclarant de pointeur. Donc, tout ce qui suit est le même que sans parenthèses (sauf pour l'une des fonctions '"()": int (((* p))); void ((g (void))); int * (a [1]); void (* (p ()))
Johannes Schaub - litb
2
Bravo dans votre explication. Pour une référence approfondie sur la priorité et l'associativité des opérateurs, reportez-vous à la page 53 du langage de programmation C (ANSI C deuxième édition) de Brian Kernighan et Dennis Ritchie. Les opérateurs ( ) [ ] s'associent de gauche à droite et ont une priorité plus élevée que *celle lue int* arr[8]comme un tableau de taille 8 où chaque élément pointe vers un int et int (*arr)[8]comme un pointeur vers un tableau de taille 8 qui contient des entiers
Mushy
267

Utilisez le programme cdecl , comme suggéré par K&R.

$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

Cela fonctionne aussi dans l'autre sens.

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )
sigjuice
la source
@ankii La plupart des distributions Linux devraient avoir un package. Vous pouvez également créer votre propre binaire.
sigjuice du
ah désolé de ne pas avoir mentionné, macOS ici. verra si disponible, sinon le site Web est très bien aussi. ^^ merci de m'avoir informé de cela .. N'hésitez pas à signaler NLN.
ankii
2
@ankii Vous pouvez installer depuis Homebrew (et peut-être MacPorts?). Si ceux-ci ne sont pas à votre goût, il est trivial de créer le vôtre à partir du lien Github en haut à droite de cdecl.org (je viens de le construire sur macOS Mojave). Ensuite, copiez simplement le binaire cdecl dans votre PATH. Je recommande $ PATH / bin, car il n'est pas nécessaire d'impliquer root dans quelque chose d'aussi simple que cela.
sigjuice du
Oh, je n'avais pas lu le petit paragraphe sur l'installation dans le fichier Lisez-moi. juste quelques commandes et drapeaux pour gérer les dépendances. Installé en utilisant brew. :)
ankii
1
Quand j'ai lu ceci pour la première fois, j'ai pensé: "Je ne descendrai jamais à ce niveau". Le lendemain, je l'ai téléchargé.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
126

Je ne sais pas s'il a un nom officiel, mais je l'appelle Thingy Droite-Gauche (TM).

Commencez par la variable, puis allez à droite, à gauche, à droite ... et ainsi de suite.

int* arr1[8];

arr1 est un tableau de 8 pointeurs vers des entiers.

int (*arr2)[8];

arr2 est un pointeur (la parenthèse bloque la droite-gauche) sur un tableau de 8 entiers.

int *(arr3[8]);

arr3 est un tableau de 8 pointeurs vers des entiers.

Cela devrait vous aider avec des déclarations complexes.

GManNickG
la source
19
Je l'ai entendu parler du nom de "The Spiral Rule", qui peut être trouvé ici .
fouric
6
@InkBlend: La règle en spirale est différente de la règle droite-gauche . Le premier échoue dans des cas comme int *a[][10]pendant que le second réussit.
legends2k
1
@dogeen Je pensais que ce terme avait quelque chose à voir avec Bjarne Stroustrup :)
Anirudh Ramanathan
1
Comme l'ont dit InkBlend et legends2k, il s'agit de la règle spirale qui est plus complexe et ne fonctionne pas dans tous les cas, il n'y a donc aucune raison de l'utiliser.
kotlomoy
N'oubliez pas l'associativité de ( ) [ ]gauche à droite et de droite à gauche* &
Mushy
28
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5 
Sunil bn
la source
Le 3ème ne devrait-il pas être: a est un tableau de pointeurs vers un tableau entier de taille 8? Je veux dire que chacun des tableaux entiers sera de taille 8, n'est-ce pas?
Rushil Paul
2
@Rushil: non, le dernier indice ( [5]) représente la dimension intérieure. Cela signifie que (*a[8])c'est la première dimension, et est donc la représentation externe du tableau. Ce à quoi chaque élément à l'intérieur a pointe est un tableau entier différent de taille 5.
zeboidlund
Merci pour le troisième. Je cherche comment écrire un tableau de pointeurs sur un tableau.
Deqing
15

La réponse pour les deux derniers peut également être déduite de la règle d'or en C:

La déclaration suit l'utilisation.

int (*arr2)[8];

Que se passe-t-il si vous déréférencez arr2? Vous obtenez un tableau de 8 entiers.

int *(arr3[8]);

Que se passe-t-il si vous prenez un élément arr3? Vous obtenez un pointeur sur un entier.

Cela aide également lorsque vous traitez des pointeurs vers des fonctions. Pour prendre l'exemple de sigjuice:

float *(*x)(void )

Que se passe-t-il lorsque vous déréférencez x? Vous obtenez une fonction que vous pouvez appeler sans arguments. Que se passe-t-il lorsque vous l'appelez? Il renverra un pointeur sur a float.

La priorité des opérateurs est cependant toujours délicate. Cependant, l'utilisation de parenthèses peut également être source de confusion car la déclaration suit l'utilisation. Au moins, pour moi, cela arr2ressemble intuitivement à un tableau de 8 pointeurs vers des pouces, mais c'est en fait l'inverse. Il suffit de s'y habituer. Raison suffisante pour toujours ajouter un commentaire à ces déclarations, si vous me demandez :)

modifier: exemple

Soit dit en passant, je suis juste tombé sur la situation suivante: une fonction qui a une matrice statique et qui utilise l'arithmétique du pointeur pour voir si le pointeur de ligne est hors limites. Exemple:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i &lt; 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

Production:

0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

Notez que la valeur de border ne change jamais, donc le compilateur peut optimiser cela. Ceci est différent de ce que vous voudrez peut-être utiliser initialement const int (*border)[3]:: qui déclare border comme pointeur vers un tableau de 3 entiers qui ne changera pas de valeur tant que la variable existe. Cependant, ce pointeur peut être pointé vers n'importe quel autre tableau de ce type à tout moment. Nous voulons plutôt ce type de comportement pour l'argument (car cette fonction ne modifie aucun de ces entiers). La déclaration suit l'utilisation.

(ps: n'hésitez pas à améliorer cet échantillon!)

Hraban Luyat
la source
5
typedef int (*PointerToIntArray)[];
typedef int *ArrayOfIntPointers[];
Byron Formwalt
la source
3

En règle générale, opérateurs unaires à droite (comme [], (), etc.) donnent leur préférence sur les gauche. Donc, ce int *(*ptr)()[];serait un pointeur qui pointe vers une fonction qui renvoie un tableau de pointeurs à int (obtenez les bons opérateurs dès que vous le pouvez en sortant de la parenthèse)

Luis Colorado
la source
C'est vrai, mais c'est aussi ilegal. Vous ne pouvez pas avoir de fonction qui renvoie un tableau. J'ai essayé et obtenu ceci: error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];sous GCC 8 avec$ gcc -std=c11 -pedantic-errors test.c
Cacahuete Frito
1
La raison pour laquelle le compilateur donne cette erreur est qu'il interprète la fonction comme renvoyant un tableau, comme l'indique l'interprétation correcte de la règle de priorité. Il est ilegal en tant que déclaration, mais la déclaration légale int *(*ptr)();permet d'utiliser une expression comme p()[3](ou (*p)()[3]) plus tard.
Luis Colorado
Ok, si je comprends bien, vous parlez de créer une fonction qui renvoie un pointeur vers le premier élément d'un tableau (pas un tableau lui-même), et d'utiliser plus tard cette fonction comme si elle retournait un tableau? Idée intéressante. Je vais l'essayer. int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }et appelez-le comme ceci: foo(arr)[4];qui devrait contenir arr[2][4], non?
Cacahuete Frito
à droite ... mais vous aviez raison aussi, et la déclaration était illégale. :)
Luis Colorado
2

Je pense que nous pouvons utiliser la règle simple ..

example int * (*ptr)()[];
start from ptr 

" ptrest un pointeur vers" aller vers la droite .. ses ")" maintenant aller vers la gauche c'est un "(" sortir sortir vers la droite "()" donc "vers une fonction qui ne prend aucun argument" aller vers la gauche "et retourner un pointeur" aller à droite "vers un tableau" aller à gauche "d'entiers"

simal
la source
J'améliorerais un peu cela: "ptr est un nom qui fait référence à" aller à droite ... c'est ), maintenant aller à gauche ... c'est *"un pointeur pour" aller à droite ... c'est ), maintenant aller à gauche ... c'est a (sortir, aller à droite ()donc "vers une fonction qui ne prend aucun argument" aller à droite ... []"et retourne un tableau de" aller à droite ;, donc aller à gauche ... *"pointeurs vers" aller à gauche ... int"entiers"
Cacahuete Frito
2

Voici comment je l'interprète:

int *something[n];

Remarque sur la priorité: l'opérateur d'indice de tableau ( []) a une priorité plus élevée que l'opérateur de déréférence ( *).

Donc, ici, nous appliquerons l' []avant *, ce qui rend la déclaration équivalente à:

int *(something[i]);

Remarque sur le sens d'une déclaration: int nummoyens numest unint , int *ptrou int (*ptr)moyens, (valeur at ptr) est un int, qui fait ptrun pointeur vers int.

Cela peut être lu comme, (la valeur de la (valeur au ième index du quelque chose)) est un entier. Ainsi, (valeur au ième index de quelque chose) est un (pointeur entier), ce qui fait de quelque chose un tableau de pointeurs entiers.

Dans le second,

int (*something)[n];

Pour donner un sens à cette déclaration, vous devez être familier avec ce fait:

Remarque sur la représentation du pointeur du tableau: somethingElse[i]équivaut à*(somethingElse + i)

Donc, en remplaçant somethingElsepar (*something), nous obtenons *(*something + i), qui est un entier selon la déclaration. Donc, (*something)nous a donné un tableau, ce qui fait quelque chose d'équivalent à (pointeur vers un tableau) .

nishantbhardwaj2002
la source
0

Je suppose que la deuxième déclaration prête à confusion pour beaucoup. Voici un moyen simple de le comprendre.

Permet d'avoir un tableau d'entiers, c'est à dire int B[8].

Ayons également une variable A qui pointe vers B. Maintenant, la valeur en A est B, c'est-à-dire (*A) == B. Par conséquent, A pointe vers un tableau d'entiers. Dans votre question, arr est similaire à A.

De même, dans int* (*C) [8], C est un pointeur vers un tableau de pointeurs sur entier.

nishantbhardwaj2002
la source
0
int *arr1[5]

Dans cette déclaration, arr1est un tableau de 5 pointeurs vers des entiers. Motif: les crochets ont une priorité plus élevée que * (opérateur de déréfernçage). Et dans ce type, le nombre de lignes est fixe (5 ici), mais le nombre de colonnes est variable.

int (*arr2)[5]

Dans cette déclaration, arr2est un pointeur vers un tableau entier de 5 éléments. Raison: ici, les crochets () ont une priorité plus élevée que []. Et dans ce type, le nombre de lignes est variable, mais le nombre de colonnes est fixe (5 ici).

Krishna Kanth Yenumula
la source
-7

Dans le pointeur vers un entier si le pointeur est incrémenté, il passe à l'entier suivant.

dans le tableau du pointeur si le pointeur est incrémenté, il passe au tableau suivant

Nikhil
la source
" dans le tableau du pointeur si le pointeur est incrémenté, il passe au tableau suivant " c'est tout à fait faux.
alk