web-dev-qa-db-fra.com

Créer un pointeur sur un tableau à deux dimensions

J'ai besoin d'un pointeur sur un tableau statique à 2 dimensions. Comment est-ce fait?

static uint8_t l_matrix[10][20];

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

Je reçois toutes sortes d'erreurs comme:

  • warning: affectation de type de pointeur incompatible
  • la valeur en indice n'est ni un tableau ni un pointeur
  • erreur: utilisation non valide du membre de tableau flexible
102
Dill

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

uint8_t (*matrix_ptr)[20] = l_matrix;

Avec typedef, cela a l'air 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 pointeur/tableau world en C, il y a beaucoup de confusion autour de cela.


Modifier

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 leur comportement n'a pas été montré. Voici comment ils font

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

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

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

Ensuite, on crée un pointeur sur un tableau incomplet de type éléments de type tableau de 20 uint8_t. Comme le pointeur se trouve sur un tableau de tableaux, vous devez y accéder avec

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

Et parce que c'est un pointeur sur un tableau incomplet, vous ne pouvez pas faire un raccourci

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

Parce que l'indexation nécessite la connaissance de la taille du type d'élément (l'indexation implique l'ajout d'un entier au pointeur, de sorte que 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, et 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 l'affichez sous la forme d'un tableau à une dimension, est nonuint8_t, mais uint8_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 appliquer sizeof dessus

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

Il existe une autre réponse qui tire parti du fait que les éléments d’un tableau sont stockés de manière contiguë.

uint8_t *matrix_ptr = l_matrix[0];

Désormais, cela ne vous permet formellement que d'accéder aux éléments du premier élément du tableau à deux dimensions. C'est-à-dire que la condition suivante est valable

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 jusqu'à 10*20-1, mais si vous lancez une analyse d'alias et d'autres optimisations agressives, certains compilateurs peuvent supposer que ce code peut être cassé. Cela dit, je n’ai jamais rencontré de compilateur qui échoue (mais là encore, je n’ai pas utilisé cette technique dans le code réel), et même le C FAQ contient cette technique (avec un avertissement sur son UB'ness), et si vous ne pouvez pas changer le type de tableau, ceci est une dernière option pour vous sauver :)

126

Pour pleinement comprendre ceci, vous devez saisir les concepts suivants:

Les tableaux ne sont pas des pointeurs!

Tout d’abord (et cela a été suffisamment prêché), les tableaux ne sont pas des indicateurs. Au lieu de cela, dans la plupart des utilisations, ils "se désintègrent" en l'adresse de leur premier élément, qui peut être assigné à 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, de sorte que le contenu du tableau puisse être consulté sans être copié. Il s’agit simplement d’un comportement de types de tableaux et n’implique pas qu’ils sont identiques.



Tableaux multidimensionnels

Les tableaux multidimensionnels ne sont qu'un moyen de "partitionner" la mémoire de manière à ce que le compilateur/machine puisse comprendre et opérer.

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

L'avantage par rapport à l'utilisation de int a[4][3][5] par rapport à plain int b[60] est qu'ils sont maintenant «partitionnés» (il est plus facile de travailler avec leurs «morceaux») et que le programme peut maintenant effectuer une vérification liée.

En fait, int a[4][3][5] est stocké exactement comme int b[60] en mémoire - La différence avec {seulement} _ est que le programme le gère maintenant comme s'il s'agissait d'entités séparées de certaines tailles (spécifiquement, quatre groupes de trois groupes de cinq).

Gardez à l'esprit: int a[4][3][5] et int b[60] sont identiques en mémoire, et la seule différence réside dans 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}
}

A partir de cela, vous pouvez clairement voir que chaque "partition" est juste un tableau que le programme garde en mémoire.



Syntaxe

Maintenant, _ = 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 oeil à 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 assigné à un pointeur si directement:

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

int *p2 = a; // WRONG !

Pourquoiane peut-il pas être affecté à un pointeur maisa[0]peut?

Ceci est simplement une conséquence de tableaux multidimensionnels, et je vais 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 première dimension. Par conséquent, en ce qui concerne le programme, nous examinons simplement 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?

Il est préférable de penser de cette façon:

Une 'décomposition' d'un tableau multidimensionnel n'est pas simplement une adresse, mais une adresse avec des données de partition (AKA comprend toujours que ses données sous-jacentes sont composées d'autres tableaux), qui consiste en des limites définies par le tableau. au-delà de la première dimension.

Cette logique de "partition" ne peut exister dans un pointeur à moins que nous ne le spécifiions:

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] - Cela indique que nous créons un pointeur avec ces limites, et non un tableau de pointeurs contenant ces limites: int *p[5][95][8]



Conclusion

Revoyons:

  • Les tableaux se décomposent en adresses s'ils n'ont pas d'autre objectif dans le contexte utilisé
  • Les tableaux multidimensionnels ne sont que des tableaux de tableaux - Par conséquent, l'adresse "en décomposition" portera la charge de "J'ai des sous-dimensions"
  • Les données de dimension ne peuvent pas exister dans un pointeur sauf si vous le lui donnez.

En bref: les matrices multidimensionnelles se désintègrent en adresses capables de comprendre leur contenu.

20
Super Cat

Dans

int *ptr= l_matrix[0];

vous pouvez accéder comme 

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

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

7
Sagar

En C99 (supporté par clang et gcc), il existe une syntaxe obscure pour passer des tableaux multidimensionnels à des 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 simple pointeur, ceci laisse deviner la taille du tableau, théoriquement , ce qui permet au compilateur de se mettre en garde de passer un tableau trop petit et de détecter les accès évidents hors limites.

Malheureusement, cela ne résout pas sizeof() et les compilateurs ne semblent pas utiliser cette information pour le moment, cela reste donc une curiosité.

5
Kornel

G'day

La déclaration

static uint8_t l_matrix[10][20];

a réservé un espace de stockage pour 10 rangées de 20 emplacements unit8_t, c'est-à-dire 200 emplacements de taille uint8_t, , chaque élément étant trouvé en calculant 20 x rangées + colonnes.

Donc ne

uint8_t (*matrix_ptr)[20] = l_matrix;

vous donne ce dont vous avez besoin et pointez sur l'élément de colonne zéro de la première ligne du tableau?

Edit: En y réfléchissant un peu plus loin, un nom de tableau n'est-il pas, par définition, un pointeur? En d’autres termes, le nom d’un tableau est synonyme de l’emplacement du premier élément, c.-à-d. L_matrix [0] [0]?

Edit2: Comme mentionné par d'autres, l'espace de commentaires 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 le tableau 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 de pointeur prennent automatiquement en compte la taille de l'objet pointé. - Section K & R 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 concerné. Le compilateur ajustera automatiquement pour ceux-ci. Par définition de la norme C ANSI.

HTH

à votre santé,

5
Rob Wells

Vous pouvez toujours éviter de jouer avec le compilateur en déclarant le tableau comme linéaire et en effectuant vous-même le calcul de l'index (ligne, colonne) d'un 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.

4
gnosis

Vous pouvez le faire comme ça:

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

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 
}
1
Ken Keenan

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 génère une erreur ou un avertissement, vous pouvez utiliser:

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