45 votes

Les différences lors de l'utilisation ** dans le C

J'ai commencé à apprendre le C récemment, et je vais avoir un problème de compréhension de la syntaxe de pointeur, par exemple quand j'écris la ligne suivante:

int ** arr = NULL;

Comment puis-je savoir si:

  • arr est un pointeur vers un pointeur d'entier

  • arr est un pointeur vers un tableau de pointeurs vers des entiers

  • arr est un pointeur vers un tableau de pointeurs vers des tableaux d'entiers

N'est-il pas tout de même à l' int ** ?


Une autre question, pour le même problème:

Si j'ai une fonction qui reçoit char ** s comme paramètre, je veux faire référence à elle comme un pointer d'un tableau de chaînes de caractères, soit un pointeur vers un tableau de pointeurs vers un tableau d' chars, mais est-il aussi un pointeur vers un pointeur vers un char?

44voto

StoryTeller Points 6139

N'est-il pas tout de même à l' int **?

Vous venez de découvrir ce qui peut être considéré comme une faille dans le système de type. Chaque option que vous avez spécifié peut être vrai. C'est essentiellement dérivée d'une vue à plat d'un des programmes de la mémoire, où une seule adresse peut être utilisée pour faire référence à différentes logiques mises en mémoire.

La voie C programmeurs ont eu affaire avec ce depuis que C de la création, est en mettant une convention en place. Comme exigeant la taille du paramètre(s) pour les fonctions que d'accepter de tels pointeurs, et de documenter leurs hypothèses au sujet de la disposition de la mémoire. Ou en exigeant que les tableaux être résilié avec une valeur spéciale, permettant ainsi aux "irréguliers" tampons de pointeurs vers des tampons.


Je ressens une certaine quantité de clarification est dans l'ordre. Comme vous pouvez vous voir lors de la consultation de l'autre de très bonnes réponses ici, les tableaux ne sont pas des pointeurs. Ils font toutefois de désintégration dans ceux de suffisamment de contextes pour justifier une longues décennies d'erreur dans l'enseignement à leur sujet (mais je m'égare).

Ce que j'ai écrit à l'origine désigne le code comme suit:

void func(int **p_buff)
{
}

//...

int a = 0, *pa = &a;
func(&pa);

//...

int a[3][10];
int *a_pts[3] = { a[0], a[1], a[2] };
func(a_pts);

//...

int **a = malloc(10 * sizeof *a);
for(int i = 0; i < 10; ++i)
  a[i] = malloc(i * sizeof *a[i]);
func(a);

Supposons func et chaque fragment de code est compilé dans une autre unité de traduction. Chaque exemple (sauf les fautes de frappe par moi) est valable C. Les tableaux se désintègre en un "pointeur vers un pointeur" quand passés comme arguments. Quelle est la définition de l' func savoir ce qu'est exactement il a été adopté par le type de son paramètre à lui tout seul!? La réponse est qu'il ne peut pas. Le type statique de l' p_buff est int**, mais elle permet tout de même func d'accéder indirectement à des (parties de) des objets très différents types efficaces.

27voto

Antti Haapala Points 11542

La déclaration int **arr dit: "déclarer arr comme un pointeur vers un pointeur vers un entier". (Si valide) points pour un seul pointeur qui pointe (si valide) pour un entier unique objet. Comme il est possible d'utiliser l'arithmétique des pointeurs avec un niveau d'indirection (c - *arr est le même que arr[0] et **arr est le même que arr[0][0]) , l'objet peut être utilisé pour accéder à toutes les 3 de votre question (qui est, pour la seconde, l'accès à un tableau de pointeurs vers des entiers, et pour la troisième, accéder à un tableau de pointeurs vers les premiers éléments de l'entier des tableaux), à condition que les pointeurs pointent vers la première éléments des tableaux...


Pourtant, arr est toujours déclaré comme un pointeur vers un unique pointeur vers un seul objet integer. Il est également possible de déclarer un pointeur vers un tableau de défini dimensions. Ici, a est déclaré comme un pointeur de tableau de 10 éléments de pointeurs vers des tableaux de 10 entiers:

cdecl> declare a as pointer to array 10 of pointer to array 10 of int;
int (*(*a)[10])[10]

Dans la pratique, la matrice de pointeurs qui sont plus utilisés pour le passage dans les tableaux multidimensionnels de la constante dimensions sur des fonctions, et pour le passage dans la variable de tableaux de longueur. La syntaxe pour déclarer une variable comme un pointeur vers un tableau est rarement vu, comme chaque fois qu'ils sont passés dans une fonction, il est un peu plus facile à utiliser les paramètres de type "tableau de taille non définie" à la place, donc, au lieu de déclarer

void func(int (*a)[10]);

on pourrait utiliser

void func(int a[][10])

pour passer dans un tableau multidimensionnel de tableaux de 10 entiers. Sinon, typedef peut être utilisé pour atténuer les maux de tête.

9voto

Lundin Points 21616

Comment puis-je savoir si :

  • arr est un pointeur vers un pointeur d'entier

C'est toujours un pointeur de pointeur d'entier.

  • arr est un pointeur vers un tableau de pointeurs vers des entiers
  • arr est un pointeur vers un tableau de pointeurs vers des tableaux d'entiers

Il ne peut jamais l'être. Un pointeur vers un tableau de pointeurs vers des entiers serait déclaré comme ceci:

int* (*arr)[n]

Il semble que si vous ont été trompés à utiliser int** par les pauvres enseignants/livres/tutoriels. Il est presque toujours incorrecte de la pratique, comme expliqué ici et ici et la ( avec des explications détaillées à propos de la matrice de pointeurs) ici.

MODIFIER

Enfin eu le temps de l'écriture détaillée poste en expliquant ce que les tableaux sont, quel look-up tables, pourquoi ces derniers sont mauvais et que vous devez utiliser à la place: Correctement l'allocation de matrices multi-dimensionnelles.

8voto

Stephan Lechner Points 29375

Avoir uniquement la déclaration de la variable, vous ne pouvez pas distinguer les trois cas. On peut toujours discuter si on ne devrait pas utiliser quelque chose comme int *x[10] à exprimer un tableau de 10 pointeurs vers des entiers ou d'autre chose; mais, int **x peut - en raison de l'arithmétique de pointeur, être utilisé dans les trois façons différentes, dans chaque sens en supposant une autre disposition de la mémoire avec le (bon) chance de faire l'hypothèse erronée.

Prenons l'exemple suivant, où l' int ** est utilisé de trois façons différentes, c'est à dire p2p2i_v1 comme un pointeur vers un pointeur vers un (unique) de type int, p2p2i_v2 comme un pointeur vers un tableau de pointeurs de type int, et p2p2i_v3 comme un pointeur vers un pointeur vers un tableau d'entiers. Notez que vous ne pouvez pas distinguer ces trois significations uniquement par le type, qui est - int** pour tous les trois. Mais avec différentes initialisations, accès à chacun d'eux dans le mauvais sens, les rendements quelque chose d'imprévisible, à l'exception de l'accès aux éléments premiers:

int i1=1,i2=2,i3=3,i4=4;

int *p2i = &i1;
int **p2p2i_v1 = &p2i;  // pointer to a pointer to a single int

int *arrayOfp2i[4] = { &i1, &i2, &i3, &i4 };
int **p2p2i_v2 = arrayOfp2i; // pointer to an array of pointers to int

int arrayOfI[4] = { 5,6,7,8 };
int *p2arrayOfi = arrayOfI;
int **p2p2i_v3 = &p2arrayOfi; // pointer to a pointer to an array of ints

// assuming a pointer to a pointer to a single int:
int derefi1_v1 = *p2p2i_v1[0];  // correct; yields 1
int derefi1_v2 = *p2p2i_v2[0];  // correct; yields 1
int derefi1_v3 = *p2p2i_v3[0];  // correct; yields 5

// assuming a pointer to an array of pointers to int's
int derefi1_v1_at1 = *p2p2i_v1[1];  // incorrect, yields ? or seg fault
int derefi1_v2_at1 = *p2p2i_v2[1]; // correct; yields 2
int derefi1_v3_at1 = *p2p2i_v3[1]; // incorrect, yields ? or seg fault


// assuming a pointer to an array of pointers to an array of int's
int derefarray_at1_v1 = (*p2p2i_v1)[1]; // incorrect; yields ? or seg fault;
int derefarray_at1_v2 = (*p2p2i_v2)[1]; // incorrect; yields ? or seg fault;
int derefarray_at1_v3 = (*p2p2i_v3)[1]; // correct; yields 6;

7voto

Magisch Points 2913

Comment puis-je savoir si :

arr est un pointeur vers un pointeur d'entier

arr est un pointeur vers un tableau de pointeurs vers des entiers

arr est un pointeur vers un tableau de pointeurs vers des tableaux d'entiers

Vous ne pouvez pas. Il peut être l'un de ceux-ci. Ce qu'il finit par être dépend de la façon dont vous allouer / utiliser.

Donc, si vous écrivez du code à l'aide de ces, le document que vous êtes en train de faire, passer des paramètres de taille pour les fonctions à l'aide d'eux, et généralement être sûr de ce que vous avez alloué avant de l'utiliser.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X