web-dev-qa-db-fra.com

Passer un tableau multidimensionnel de longueur variable à une fonction

Il y a des tonnes de questions similaires, mais je n'ai toujours pas trouvé de réponse pertinente pour la fonctionnalité des tableaux de longueur variable dans C99/C11.

Comment passer tableau multidimensionnel de longueur variable à une fonction en C99/C11?

Par exemple:

void foo(int n, int arr[][]) // <-- error here, how to fix?
{
}

void bar(int n)
{
    int arr[n][n];
    foo(n, arr);
}

Le compilateur (g++-4.7 -std=gnu++11) Dit:
error: declaration of ‘arr’ as multidimensional array must have bounds for all dimensions except the first

Si je le change en int *arr[], Le compilateur se plaint toujours:
error: cannot convert ‘int (*)[(((sizetype)(((ssizetype)n) + -1)) + 1)]’ to ‘int**’ for argument ‘2’ to ‘void foo(int, int**)’

Question suivante, comment la transmettre par valeur et comment la transmettre par référence? Apparemment, vous ne voulez généralement pas que le tableau entier soit copié lorsque vous le passez à une fonction.

Avec des tableaux de longueur constante, c'est simple, car, comme l'indique la "constante", vous devez connaître la longueur lorsque vous déclarez la fonction:

void foo2(int n, int arr[][10]) // <-- ok
{
}

void bar2()
{
    int arr[10][10];
    foo2(10, arr);
}

Je sais, passer des tableaux à des fonctions comme celle-ci n'est pas une meilleure pratique, et je ne l'aime pas du tout. Il vaut probablement mieux faire avec des pointeurs plats, ou des objets (comme std: vector) ou autrement. Mais quand même, je suis un peu curieux de savoir quelle est la réponse ici d'un point de vue théorique.

35
alveko

Passer des tableaux à des fonctions est un peu drôle en C et C++. Il n'y a pas de valeur de type de tableau, vous passez donc un pointeur.

Pour adresser un tableau 2D (un vrai, pas un tableau de tableaux), vous devrez passer 2 blocs de données:

  • le pointeur vers l'endroit où il commence
  • la largeur d'une rangée

Et ce sont deux valeurs distinctes, que ce soit C ou C++ ou avec VLA ou sans ou autre chose.

Quelques façons d'écrire cela:

Le plus simple, fonctionne partout mais nécessite plus de travail manuel

void foo(int width, int* arr) {
    arr[x + y*width] = 5;
}

VLA, standard C99

void foo(int width, int arr[][width]) {
    arr[x][y] = 5;
}

VLA avec arguments inversés, déclaration de paramètres avant (extension GNU C)

void foo(int width; int arr[][width], int width) {
    arr[x][y]=5;
}

C++ w/VLA (extension GNU C++, terriblement moche)

void foo(int width, int* ptr) {
    typedef int arrtype[][width];
    arrtype& arr = *reinterpret_cast<arrtype*>(ptr);
    arr[x][y]=5;
}

Grande remarque:

La notation [x] [y] avec un tableau 2D fonctionne car le type du tableau contient la largeur. Aucun type de tableau VLA = ne doit être corrigé au moment de la compilation.

Par conséquent: si vous ne pouvez pas utiliser VLA, alors ...

  • il n'y a aucun moyen de le gérer en C,
  • il n'y a aucun moyen de le gérer sans une classe proxy avec surcharge d'opérateur surchargée en C++.

Si vous pouvez utiliser VLA (C99 ou GNU C++), alors ...

  • tu es dans le vert en C,
  • vous avez toujours besoin d'un gâchis en C++, utilisez plutôt des classes.

Pour C++, boost::multi_array est un choix solide.

Une solution de contournement

Pour les tableaux 2D, vous pouvez effectuer deux allocations distinctes:

  • un tableau 1D de pointeurs vers T (A)
  • un tableau 2D de T (B)

Réglez ensuite les pointeurs dans (A) pour qu'ils pointent dans les rangées respectives de (B).

Avec cette configuration, vous pouvez simplement passer (A) comme un simple T** et il se comportera bien avec [x][y] indexation.

Cette solution est sympa pour la 2D, mais a besoin de plus en plus de passe-partout pour des dimensions plus élevées. Elle est également plus lente que la solution VLA en raison de la couche supplémentaire d'indirection.

Vous pouvez également exécuter une solution similaire avec une allocation distincte pour chaque ligne de B. En C, cela ressemble à un malloc-in-a-loop, et est analogue au vecteur de vecteurs de C++ Cependant, cela enlève l'avantage d'avoir tout le tableau dans un seul bloc.

43
Kos

Il n'y a pas de méthode claire pour cela, mais vous pouvez utiliser une solution de contournement pour traiter un tableau bidimensionnel comme un tableau unidimensionnel, puis le reconvertir en un tableau bidimensionnel à l'intérieur de la fonction.

void foo2(int n, int *arr) 
{
    int *ptr; // use this as a marker to go to next block
    int i;
    int j;

    for(i = 0; i < n; i++)
    {
        ptr = arr + i*n; // this is the starting for arr[i] ...
        for (j = 0; j < n ;j++)
        {
            printf(" %d ", ptr[j]); // This is same as arr[i][j]
        }
    }
}

void bar2()
{
    int arr[10][10];
    foo2(10, (int *)arr);
}
1
Pankaj Rai