web-dev-qa-db-fra.com

Pointeur C vers tableau bidimensionnel

Je sais qu'il y a plusieurs questions à ce sujet qui donnent de bonnes solutions (et qui fonctionnent), mais aucune à mon humble avis qui dit clairement quelle est la meilleure façon d'y parvenir. Supposons donc que nous ayons un tableau 2D:

int tab1[100][280];

Nous voulons créer un pointeur qui pointe vers ce tableau 2D. Pour y parvenir, nous pouvons faire:

int (*pointer)[280]; // pointer creation
pointer = tab1; //assignation
pointer[5][12] = 517; // use
int myint = pointer[5][12]; // use

ou bien :

int (*pointer)[100][280]; // pointer creation
pointer = &tab1; //assignation
(*pointer)[5][12] = 517; // use
int myint = (*pointer)[5][12]; // use 

OK, les deux semblent bien fonctionner. Maintenant je voudrais savoir:

  • quelle est la meilleure façon, la 1ère ou la 2ème?
  • sont les deux égaux pour le compilateur? (vitesse, perf ...)
  • l'une de ces solutions consomme-t-elle plus de mémoire que l'autre?
  • quelle est la plus fréquemment utilisée par les développeurs?
27
Chrysotribax
//defines an array of 280 pointers (1120 or 2240 bytes)
int  *pointer1 [280];

//defines a pointer (4 or 8 bytes depending on 32/64 bits platform)
int (*pointer2)[280];      //pointer to an array of 280 integers
int (*pointer3)[100][280]; //pointer to an 2D array of 100*280 integers

En utilisant pointer2 ou pointer3 produit le même binaire sauf les manipulations que ++pointer2 comme indiqué par WhozCraig .

Je recommande d'utiliser typedef (produisant le même code binaire que ci-dessus pointer3)

typedef int myType[100][280];
myType *pointer3;

Remarque: Depuis C++ 11, vous pouvez également utiliser le mot clé using au lieu de typedef

using myType = int[100][280];
myType *pointer3;

dans votre exemple:

myType *pointer;                // pointer creation
pointer = &tab1;                // assignation
(*pointer)[5][12] = 517;        // set (write)
int myint = (*pointer)[5][12];  // get (read)

Remarque: Si le tableau tab1 est utilisé dans un corps de fonction => ce tableau sera placé dans la mémoire de la pile d'appels. Mais la taille de la pile est limitée. L'utilisation de tableaux plus grands que la pile de mémoire libre produit un débordement de pile crash .

L'extrait complet est compilable en ligne sur gcc.godbolt.org

int main()
{
    //defines an array of 280 pointers (1120 or 2240 bytes)
    int  *pointer1 [280];
    static_assert( sizeof(pointer1) == 2240, "" );

    //defines a pointer (4 or 8 bytes depending on 32/64 bits platform)
    int (*pointer2)[280];      //pointer to an array of 280 integers
    int (*pointer3)[100][280]; //pointer to an 2D array of 100*280 integers  
    static_assert( sizeof(pointer2) == 8, "" );
    static_assert( sizeof(pointer3) == 8, "" );

    // Use 'typedef' (or 'using' if you use a modern C++ compiler)
    typedef int myType[100][280];
    //using myType = int[100][280];

    int tab1[100][280];

    myType *pointer;                // pointer creation
    pointer = &tab1;                // assignation
    (*pointer)[5][12] = 517;        // set (write)
    int myint = (*pointer)[5][12];  // get (read)

    return myint;
}
25
olibre

int *pointer[280]; // Crée 280 pointeurs de type int.

Dans un système d'exploitation 32 bits, 4 octets pour chaque pointeur. donc 4 * 280 = 1120 octets.

int (*pointer)[100][280]; // Crée un seul pointeur qui est utilisé pour pointer un tableau de [100] [280] ints.

Ici seulement 4 octets.

Pour en venir à votre question, int (*pointer)[280]; et int (*pointer)[100][280]; sont différents bien qu'ils pointent vers le même tableau 2D de [100] [280].

Parce que si int (*pointer)[280]; est incrémenté, alors il pointe vers le tableau 1D suivant, mais là où int (*pointer)[100][280]; traverse le tableau 2D tout entier et pointe vers l'octet suivant. L'accès à cet octet peut poser problème si cette mémoire n'appartient pas à votre processus.

9
Jeyaram

Vos deux exemples sont équivalents. Cependant, le premier est moins évident et plus "hacky", tandis que le second indique clairement votre intention.

int (*pointer)[280];
pointer = tab1;

pointer pointe vers un tableau 1D de 280 entiers. Dans votre affectation, vous attribuez en fait la première ligne de tab1. Cela fonctionne car vous pouvez implicitement convertir des tableaux en pointeurs (vers le premier élément).

Lorsque vous utilisez pointer[5][12], C traite pointer comme un tableau de tableaux (pointer[5] est de type int[280]), il y a donc un autre implicite transtypé ici (au moins sémantiquement).

Dans votre deuxième exemple, vous créez explicitement un pointeur vers un tableau 2D:

int (*pointer)[100][280];
pointer = &tab1;

La sémantique est plus claire ici: *pointer est un tableau 2D, vous devez donc y accéder en utilisant (*pointer)[i][j].

Les deux solutions utilisent la même quantité de mémoire (1 pointeur) et fonctionneront probablement aussi rapidement. Sous le capot, les deux pointeurs pointent même vers le même emplacement mémoire (le premier élément du tab1 array), et il est possible que votre compilateur génère même le même code.

La première solution est "plus avancée" car il faut une compréhension assez approfondie du fonctionnement des tableaux et des pointeurs en C pour comprendre ce qui se passe. Le second est plus explicite.

9
Ferdinand Beyer