Créer un pointeur vers un tableau à deux dimensions


120

J'ai besoin d'un pointeur vers un tableau statique à 2 dimensions. Comment cela se fait-il?

static uint8_t l_matrix[10][20];

void test(){
   uint8_t **matrix_ptr = l_matrix; //wrong idea 
}

J'obtiens toutes sortes d'erreurs comme:

  • avertissement: affectation d'un type de pointeur incompatible
  • la valeur en indice n'est ni un tableau ni un pointeur
  • erreur: utilisation incorrecte du membre du tableau flexible


1
@ JohannesSchaub-litb Cela n'existe plus. (Comment puis-je voir encore ... Je sais que les membres à faible rep peuvent voir, mais j'ai oublié comment ...)
Mateen Ulhaq

Réponses:


140

Ici, vous voulez faire un pointeur vers le premier élément du tableau

uint8_t (*matrix_ptr)[20] = l_matrix;

Avec typedef, cela semble plus propre

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

Ensuite, vous pourrez à nouveau profiter de la vie :)

matrix_ptr[0][1] = ...;

Méfiez-vous du monde pointeur / tableau en C, il y a beaucoup de confusion autour de cela.


Éditer

Passez en revue certaines des autres réponses ici, car les champs de commentaire sont trop courts pour y figurer. Plusieurs alternatives ont été proposées, mais on n'a pas montré comment elles se comportent. Voici comment ils font

uint8_t (*matrix_ptr)[][20] = l_matrix;

Si vous corrigez l'erreur et ajoutez l'opérateur address-of &comme dans l'extrait suivant

uint8_t (*matrix_ptr)[][20] = &l_matrix;

Ensuite, celui-ci crée un pointeur vers un tableau de type incomplet d'éléments de type tableau de 20 uint8_t. Comme le pointeur est vers un tableau de tableaux, vous devez y accéder avec

(*matrix_ptr)[0][1] = ...;

Et comme il s'agit d'un pointeur vers un tableau incomplet, vous ne pouvez pas faire de raccourci

matrix_ptr[0][0][1] = ...;

Parce que l'indexation nécessite que la taille du type d'élément soit connue (l'indexation implique l'ajout d'un entier au pointeur, donc cela ne fonctionnera pas avec les types incomplets). Notez que cela ne fonctionne que dans C, car T[]et T[N]sont des types compatibles. C ++ n'a pas de concept de types compatibles , il rejettera donc ce code, car T[]et T[10]sont des types différents.


L'alternative suivante ne fonctionne pas du tout, car le type d'élément du tableau, lorsque vous le visualisez comme un tableau unidimensionnel, ne l' est pasuint8_t , maisuint8_t[20]

uint8_t *matrix_ptr = l_matrix; // fail

Ce qui suit est une bonne alternative

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

Vous y accédez avec

(*matrix_ptr)[0][1] = ...;
matrix_ptr[0][0][1] = ...; // also possible now

Il a l'avantage de préserver la taille de la dimension extérieure. Vous pouvez donc y appliquer sizeof

sizeof (*matrix_ptr) == sizeof(uint8_t) * 10 * 20

Il existe une autre réponse qui utilise le fait que les éléments d'un tableau sont stockés de manière contiguë

uint8_t *matrix_ptr = l_matrix[0];

Maintenant, cela ne vous permet formellement d'accéder qu'aux éléments du premier élément du tableau à deux dimensions. Autrement dit, la condition suivante est maintenue

matrix_ptr[0] = ...; // valid
matrix_ptr[19] = ...; // valid

matrix_ptr[20] = ...; // undefined behavior
matrix_ptr[10*20-1] = ...; // undefined behavior

Vous remarquerez que cela fonctionne probablement 10*20-1, mais si vous lancez une analyse d'alias et d'autres optimisations agressives, certains compilateurs pourraient faire une hypothèse qui pourrait casser ce code. Cela dit, je n'ai jamais rencontré de compilateur qui échoue dessus (mais là encore, je n'ai pas utilisé cette technique dans du code réel), et même la FAQ C contient cette technique (avec un avertissement sur son UB'ness ), et si vous ne pouvez pas changer le type de tableau, c'est une dernière option pour vous sauver :)


+1 - belle info sur la ventilation int (*) [] [20] - impossible de faire ça en C ++
Faisal Vali

@litb, je suis désolé mais c'est faux car votre solution ne fournit aucune allocation de stockage pour la baie.
Rob Wells

2
@Rob, je ne vous comprends pas très bien. le stockage dans tous ces cas est assuré par le tableau l_matix lui-même. Les pointeurs vers eux prennent le stockage à partir de l'endroit où ils sont déclarés dans et en tant que (pile, segment de données statique, ...).
Johannes Schaub - litb le

Juste curieux de savoir pourquoi avons-nous besoin de l'adresse "&" de l_matrix?
électro

1
@Sohaib - non, cela ne crée qu'un seul pointeur. Vous l'avez peut-être confondu avec uint8_t *d[20], qui crée un tableau de 3 pointeurs vers uint8_t, mais cela ne fonctionnerait pas dans ce cas.
Palo

28

Pour bien comprendre cela, vous devez comprendre les concepts suivants:

Les tableaux ne sont pas des pointeurs!

Tout d'abord (et cela a été assez prêché), les tableaux ne sont pas des pointeurs . Au lieu de cela, dans la plupart des utilisations, ils `` se désintègrent '' vers l'adresse de leur premier élément, qui peut être attribué à un pointeur:

int a[] = {1, 2, 3};

int *p = a; // p now points to a[0]

Je suppose que cela fonctionne de cette manière afin que le contenu du tableau soit accessible sans les copier tous. C'est juste un comportement de types de tableaux et ne veut pas dire qu'ils sont la même chose.



Tableaux multidimensionnels

Les tableaux multidimensionnels ne sont qu'un moyen de «partitionner» la mémoire d'une manière que le compilateur / la machine peut comprendre et utiliser.

Par exemple, int a[4][3][5] = un tableau contenant 4 * 3 * 5 (60) «morceaux» de mémoire de taille entière.

L'avantage sur l'utilisation de int a[4][3][5]vs plainint b[60] est qu'ils sont maintenant «partitionnés» (il est plus facile de travailler avec leurs «morceaux», si nécessaire), et le programme peut maintenant effectuer une vérification liée.

En fait, int a[4][3][5]est stocké exactement comme int b[60]dans la mémoire - La seule différence est que le programme le gère maintenant comme s'il s'agissait d'entités séparées de certaines tailles (plus précisément, quatre groupes de trois groupes de cinq).

Gardez à l'esprit: les deux int a[4][3][5]et int b[60]sont identiques en mémoire, et la seule différence est la façon dont ils sont gérés par l'application / le compilateur

{
  {1, 2, 3, 4, 5}
  {6, 7, 8, 9, 10}
  {11, 12, 13, 14, 15}
}
{
  {16, 17, 18, 19, 20}
  {21, 22, 23, 24, 25}
  {26, 27, 28, 29, 30}
}
{
  {31, 32, 33, 34, 35}
  {36, 37, 38, 39, 40}
  {41, 42, 43, 44, 45}
}
{
  {46, 47, 48, 49, 50}
  {51, 52, 53, 54, 55}
  {56, 57, 58, 59, 60}
}

À partir de là, vous pouvez clairement voir que chaque «partition» est juste un tableau dont le programme effectue le suivi.



Syntaxe

Désormais, les tableaux sont syntaxiquement différents des pointeurs . Plus précisément, cela signifie que le compilateur / la machine les traitera différemment. Cela peut sembler une évidence, mais jetez un œil à ceci:

int a[3][3];

printf("%p %p", a, a[0]);

L'exemple ci-dessus imprime deux fois la même adresse mémoire, comme ceci:

0x7eb5a3b4 0x7eb5a3b4

Cependant, un seul peut être affecté à un pointeur aussi directement :

int *p1 = a[0]; // RIGHT !

int *p2 = a; // WRONG !

Pourquoi ne peut pas a être affecté à un pointeur mais a[0] peut?

Ceci, simplement, est une conséquence des tableaux multidimensionnels, et je vais vous expliquer pourquoi:

Au niveau de « a», nous voyons toujours que nous avons une autre «dimension» à espérer. Au niveau de ' a[0]', cependant, nous sommes déjà dans la dimension supérieure, donc en ce qui concerne le programme, nous ne regardons qu'un tableau normal.

Vous demandez peut-être:

Pourquoi est-il important que le tableau soit multidimensionnel en ce qui concerne la création d'un pointeur pour lui?

Il vaut mieux penser de cette façon:

Une `` désintégration '' d'un tableau multidimensionnel n'est pas seulement une adresse, mais une adresse avec des données de partition (AKA, elle comprend toujours que ses données sous-jacentes sont constituées d'autres tableaux), qui se compose de limites définies par le tableau au-delà de la première dimension.

Cette logique de 'partition' ne peut pas exister dans un pointeur sauf si nous la spécifions:

int a[4][5][95][8];

int (*p)[5][95][8];

p = a; // p = *a[0] // p = a+0

Sinon, la signification des propriétés de tri du tableau est perdue.

Notez également l'utilisation de parenthèses autour de *p: int (*p)[5][95][8]- C'est pour spécifier que nous faisons un pointeur avec ces limites, pas un tableau de pointeurs avec ces limites:int *p[5][95][8]



Conclusion

Revoyons:

  • Les tableaux se désintègrent en adresses s'ils n'ont aucun autre objectif dans le contexte utilisé
  • Les tableaux multidimensionnels ne sont que des tableaux de tableaux - Par conséquent, l'adresse «décomposée» portera le fardeau de «J'ai des sous-dimensions»
  • Les données de dimension ne peuvent pas exister dans un pointeur à moins que vous ne les lui donniez .

En bref: les tableaux multidimensionnels se désintègrent en adresses qui permettent de comprendre leur contenu.


1
La première partie de la réponse est excellente, mais la seconde ne l'est pas. Ce n'est pas correct:, en int *p1 = &(a[0]); // RIGHT !fait, il est identique àint *p1 = a;
2501

@ 2501 Merci d'avoir repéré cette erreur, je l'ai corrigée. Je ne peux pas dire avec certitude pourquoi l'exemple qui définit cette «règle» l'a également défiée. Il convient de rappeler que ce n'est pas parce que deux entités peuvent être interprétées comme des pointeurs et donner la même valeur qu'elles ont la même signification.
Super Cat

7

Dans

int *ptr= l_matrix[0];

vous pouvez accéder comme

*p
*(p+1)
*(p+2)

après que tous les tableaux à 2 dimensions sont également stockés en 1-d.


5

G'day,

La déclaration

static uint8_t l_matrix[10][20];

a réservé un espace de stockage pour 10 lignes de 20 emplacements unit8_t, soit 200 emplacements de taille uint8_t, chaque élément étant trouvé en calculant 20 x ligne + colonne.

Donc pas

uint8_t (*matrix_ptr)[20] = l_matrix;

vous donner ce dont vous avez besoin et pointer vers l'élément de la colonne zéro de la première ligne du tableau?

Edit: En y réfléchissant un peu plus loin, un nom de tableau, par définition, n'est-il pas un pointeur? Autrement dit, le nom d'un tableau est un synonyme de l'emplacement du premier élément, ie l_matrix [0] [0]?

Edit2: Comme mentionné par d'autres, l'espace de commentaire est un peu trop petit pour une discussion plus approfondie. En tous cas:

typedef uint8_t array_of_20_uint8_t[20];
array_of_20_uint8_t *matrix_ptr = l_matrix;

ne fournit aucune allocation de stockage pour la baie en question.

Comme mentionné ci-dessus, et tel que défini par la norme, la déclaration:

static uint8_t l_matrix[10][20];

a mis de côté 200 emplacements séquentiels de type uint8_t.

Se référant à l_matrix en utilisant des déclarations de la forme:

(*l_matrix + (20 * rowno) + colno)

vous donnera le contenu de l'élément colno'th trouvé dans la ligne rowno.

Toutes les manipulations du pointeur prennent automatiquement en compte la taille de l'objet pointé. - K&R Section 5.4, p.103

C'est également le cas si un remplissage ou un décalage d'alignement d'octets est impliqué dans le stockage de l'objet à portée de main. Le compilateur les ajustera automatiquement.Par définition de la norme C ANSI.

HTH

à votre santé,


1
uint8_t (* matrix_ptr) [] [20] << les premiers crochets doivent être laissés de côté, correct est uint8_t (* matrix_ptr) [20]
Aconcagua

5

En C99 (pris en charge par clang et gcc), il existe une syntaxe obscure pour passer des tableaux multidimensionnels aux fonctions par référence:

int l_matrix[10][20];

void test(int matrix_ptr[static 10][20]) {
}

int main(void) {
    test(l_matrix);
}

Contrairement à un pointeur simple, cela donne des indications sur la taille du tableau, permettant théoriquement au compilateur d'avertir du passage d'un tableau trop petit et de repérer les accès hors limites évidents.

Malheureusement, cela ne résout pas sizeof()et les compilateurs ne semblent pas encore utiliser ces informations, cela reste donc une curiosité.


1
Cette réponse est trompeuse: cela ne fait pas de l'argument un tableau de taille fixe, c'est toujours un pointeur. static 10est une sorte de garantie qu'au moins 10 éléments sont présents, ce qui signifie encore une fois que la taille n'est pas fixe.
bluss

1
@bluss la question portait sur un pointeur, donc je ne vois pas en quoi répondre avec un pointeur (en notant par référence ) est trompeur. Le tableau est de taille fixe du point de vue de la fonction, car l'accès aux éléments au-delà de ces limites n'est pas défini.
Kornel

Je ne pense pas que l'accès au-delà de 10 soit indéfini, je ne vois rien qui l'indique.
bluss

Cette réponse semble suggérer que sans le mot-clé static, le tableau ne serait pas passé par référence, ce qui n'est pas vrai. Les tableaux sont de toute façon passés par référence. La question originale portait sur un cas d'utilisation différent - accéder aux éléments d'un tableau 2D à l'aide d'un pointeur supplémentaire dans la même fonction / espace de noms.
Palo

4

Vous pouvez toujours éviter de manipuler le compilateur en déclarant le tableau comme linéaire et en effectuant vous-même le calcul d'index (ligne, col) au tableau.

static uint8_t l_matrix[200];

void test(int row, int col, uint8_t val)

{

   uint8_t* matrix_ptr = l_matrix;
   matrix_ptr [col+y*row] = val; // to assign a value

}

c'est ce que le compilateur aurait fait de toute façon.


1
C'est ce que fait le compilateur C de toute façon. C n'a pas vraiment de notion réelle de "tableau" - la notation [] est juste du sucre syntaxique pour l'arithmétique des pointeurs
Ken Keenan

7
Cette solution a l'inconvénient de ne jamais trouver la bonne façon de procéder.
Craig McQueen

2

La syntaxe de base du pointeur d'initialisation qui pointe vers un tableau multidimensionnel est

type (*pointer)[1st dimension size][2nd dimension size][..] = &array_name

La syntaxe de base pour l'appeler est

(*pointer_name)[1st index][2nd index][...]

Voici un exemple:

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

int main() {
   // The multidimentional array...
   char balance[5][100] = {
       "Subham",
       "Messi"
   };

   char (*p)[5][100] = &balance; // Pointer initialization...

   printf("%s\n",(*p)[0]); // Calling...
   printf("%s\n",(*p)[1]); // Calling...

  return 0;
}

La sortie est:

Subham
Messi

Ça a marché...


1

Vous pouvez le faire comme ceci:

uint8_t (*matrix_ptr)[10][20] = &l_matrix;

1
cela n'occupe-t-il pas 10 * 20 octets de RAM? (im sur un microcontrôleur)
Dill

Il occupera 4 octets ou quelle que soit la taille d'un pointeur dans votre boîte. Mais rappelez-vous que si vous avez celui-ci, vous devez indexer avec matrix_ptr [0] [x] [y] ou (* matrix_ptr) [x] [y]. C'est l'interprétation directe et mot par mot du "pointeur vers un tableau bidimensionnel": p
Johannes Schaub - litb

Merci litb, j'ai oublié de mentionner comment y accéder. Il ne sert à rien de modifier ma réponse puisque vous avez fait un excellent travail avec votre réponse :)
Nick Dandoulakis

Alors, cela occupe-t-il 10 * 20 octets de RAM ou non?
Danijel

@Danijel, puisqu'il s'agit d'un pointeur vers un tableau à deux dimensions, il n'occupera que 4 octets ou quelle que soit la taille d'un pointeur dans votre boîte, soit 16 bits, 32 bits, 64 bits, etc.
Nick Dandoulakis

1

Vous voulez un pointeur sur le premier élément, donc;

static uint8_t l_matrix[10][20];

void test(){
   uint8_t *matrix_ptr = l_matrix[0]; //wrong idea 
}

0

Vous pouvez également ajouter un décalage si vous souhaitez utiliser des index négatifs:

uint8_t l_matrix[10][20];
uint8_t (*matrix_ptr)[20] = l_matrix+5;
matrix_ptr[-4][1]=7;

Si votre compilateur donne une erreur ou un avertissement, vous pouvez utiliser:

uint8_t (*matrix_ptr)[20] = (uint8_t (*)[20]) l_matrix;

Bonjour. Cette question est identifiée avec c, donc la réponse doit être dans la même langue. Veuillez observer les balises.
2501
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.