web-dev-qa-db-fra.com

Malloc un tableau tridimensionnel en C?

Je traduis du code MATLAB en C et le script que je convertis utilise beaucoup les tableaux 3D comportant 10 * 100 * 300 entrées complexes. La taille de la matrice dépend également de l'entrée du capteur. Idéalement, la matrice doit être allouée de manière dynamique. Jusqu’à présent, j’ai essayé deux approches, la première étant un tableau 1D plat le long de

value = array[x + (y*xSize) + (z*ySize*xSize)]

Ce qui blesse mon cerveau à utiliser. J'ai aussi essayé un tableau d'un tableau de pointeurs

int main () {
  int ***array = malloc(3*sizeof(int**));
  int i, j;

  for (i = 0; i < 3; i++) {
    *array[i] = malloc(3*sizeof(int*));
    for (j = 0; j < 3; j++) {
      array[i][j] = malloc(3*sizeof(int));
    }
  }

  array[1][2][1] = 10;

  return 0;
}

Ce qui donne une erreur de segmentation lorsque j'essaie d'assigner des données.

Dans un monde parfait, j'aimerais utiliser la deuxième méthode avec la notation tableau pour une programmation plus propre et plus facile. Existe-t-il un meilleur moyen d'allouer dynamiquement un tableau à trois dimensions en C?

29
Mike

J'opterais pour la première option (le tableau 1D unique) car cela vous donnerait un seul bloc de mémoire dans lequel jouer, plutôt que potentiellement des milliers de blocs de mémoire fragmentés

Si accéder au bon élément du tableau vous fait penser à cela, j'écrirais une méthode utilitaire pour convertir les emplacements x, y, z en un décalage dans le tableau 1D

int offset(int x, int y, int z) { 
    return (z * xSize * ySize) + (y * xSize) + x; 
}
18
tim_yates

Comme d'autres l'ont déjà dit, il est probablement préférable d'allouer un bloc de mémoire contigu, puis de déterminer soi-même l'indexation. Vous pouvez écrire une fonction pour le faire si vous voulez. Mais puisque vous semblez vouloir savoir comment traiter le cas de malloc(), voici un exemple:

Tout d'abord, je définis une fonction free_data(), qui libère un int *** avec xlen et ylen en tant que les deux premières tailles de dimension. Nous n'avons pas besoin d'un paramètre zlen comme free() ne prend pas la longueur du pointeur libéré.

void free_data(int ***data, size_t xlen, size_t ylen)
{
    size_t i, j;

    for (i=0; i < xlen; ++i) {
        if (data[i] != NULL) {
            for (j=0; j < ylen; ++j)
                free(data[i][j]);
            free(data[i]);
        }
    }
    free(data);
}

La fonction boucle sur le pointeur data, recherche le pointeur ith int **data[i]. Ensuite, pour un pointeur int ** donné, il parcourt celui-ci, découvrant la jth int * dans data[i][j] et le libère. Il doit également libérer data[i] une fois qu'il a libéré tous les data[i][j] et, enfin, il doit libérer lui-même data.

Passons maintenant à la fonction d’allocation. La fonction est un peu compliquée par la vérification des erreurs. En particulier, dans la mesure où il existe des appels 1 + xlen + xlen*ylenmalloc, nous devons pouvoir gérer une défaillance de l'un de ces appels et libérer toute la mémoire allouée jusqu'à présent. Pour faciliter les choses, nous nous appuyons sur le fait que free(NULL) est no-op, nous définissons donc tous les pointeurs à un niveau donné égal à NULL avant d'essayer de les allouer. Ainsi, si une erreur se produit, nous pouvons libérer tous les pointeurs.

Autre que cela, la fonction est assez simple. Nous allouons d’abord de l’espace pour les valeurs xlenint **, puis pour chacun de ces pointeurs xlen, nous allouons de l’espace pour les valeurs ylenint *, puis pour chacun de ces pointeurs xlen*ylen, nous allouons de l’espace pour les valeurs zlenint, nous laissant ainsi un espace total pour xlen*ylen*zlenint. valeurs:

int ***alloc_data(size_t xlen, size_t ylen, size_t zlen)
{
    int ***p;
    size_t i, j;

    if ((p = malloc(xlen * sizeof *p)) == NULL) {
        perror("malloc 1");
        return NULL;
    }

    for (i=0; i < xlen; ++i)
        p[i] = NULL;

    for (i=0; i < xlen; ++i)
        if ((p[i] = malloc(ylen * sizeof *p[i])) == NULL) {
            perror("malloc 2");
            free_data(p, xlen, ylen);
            return NULL;
        }

    for (i=0; i < xlen; ++i)
        for (j=0; j < ylen; ++j)
            p[i][j] = NULL;

    for (i=0; i < xlen; ++i)
        for (j=0; j < ylen; ++j)
            if ((p[i][j] = malloc(zlen * sizeof *p[i][j])) == NULL) {
                perror("malloc 3");
                free_data(p, xlen, ylen);
                return NULL;
            }

    return p;
}

Notez que j'ai un peu simplifié les appels malloc: en général, vous ne devriez pas transtyper la valeur de retour malloc et spécifier l'objet que vous affectez comme opérande à l'opérateur sizeof au lieu de son type. Cela rend les appels malloc plus simples à écrire et moins sujets aux erreurs. Vous devez inclure stdlib.h pour malloc.

Voici un programme de test utilisant les deux fonctions ci-dessus:

#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <time.h>

int main(void)
{
    int ***data;
    size_t xlen = 10;
    size_t ylen = 100;
    size_t zlen = 300;
    size_t i, j, k;

    srand((unsigned int)time(NULL));
    if ((data = alloc_data(xlen, ylen, zlen)) == NULL)
        return EXIT_FAILURE;

    for (i=0; i < xlen; ++i)
        for (j=0; j < ylen; ++j)
            for (k=0; k < zlen; ++k)
                data[i][j][k] = Rand();

    printf("%d\n", data[1][2][1]);
    free_data(data, xlen, ylen);
    return EXIT_SUCCESS;
}

Utilisez cette approche si vous le trouvez plus facile à utiliser. En général, cela sera plus lent que d'utiliser un bloc de mémoire contigu, mais si vous trouvez que la vitesse est correcte avec le schéma ci-dessus, et si cela vous simplifie la vie, vous pouvez continuer à l'utiliser. Même si vous ne l'utilisez pas, il est agréable de savoir comment faire fonctionner un tel système.

9
Alok Singhal

Êtes-vous sûr de devoir utiliser malloc? C permet de créer des tableaux multidimentionnels de manière native:

int a2[57][13][7];

Ou vous pouvez utiliser malloc de la manière suivante:

int (*a)[13][7]; // imitates 3d array with unset 3rd dimension
                 // actually it is a pointer to 2d arrays

a = malloc(57 * sizeof *a);    // allocates 57 rows

a[35][7][3] = 12; // accessing element is conventional

free(a); // freeing memory
8
Dims

C89 ne dispose d'aucun moyen de faire ce que vous désirez, car un type de tableau en C ne peut être spécifié qu'avec des valeurs connues au moment de la compilation. Donc, pour éviter l'allocation dynamique insensée, vous devrez vous en tenir à la méthode unidimensionnelle. Vous pouvez utiliser une fonction pour faciliter ce processus

int index(int x, int y, int z) {
  return x + (y*xSize) + (z*ySize*xSize);
}

int value = array[index(a, b, c)];

En C99, vous pouvez utiliser une syntaxe de tableau ordinaire même si les dimensions sont des valeurs d'exécution:

int (*array)[X][Y][Z] = (int(*)[X][Y][Z])malloc(sizeof *p); 
// fill...
int value = (*array)[a][b][c];

Cependant, cela ne fonctionne qu'avec des tableaux locaux non statiques. 

7

Oh, je déteste l'allocation de tableaux Malloc ^^

Voici une version correcte, en gros c'était juste une ligne incorrecte:

int main () {
  int ***array = (int***)malloc(3*sizeof(int**));
  int i, j;

  for (i = 0; i < 3; i++) {
    // Assign to array[i], not *array[i] (that would dereference an uninitialized pointer)
    array[i] = (int**)malloc(3*sizeof(int*));
    for (j = 0; j < 3; j++) {
      array[i][j] = (int*)malloc(3*sizeof(int));
    }
  }

  array[1][2][1] = 10;

  return 0;
}
6
AndiDog

À propos du segfault, Je suis presque sûr que quelqu'un d'autre l'a signalé, mais juste au cas où , Il y a un '*' supplémentaire dans la première ligne de la première boucle

for (i = 0; i < 3; i++) {
    *array[i] = malloc(3*sizeof(int*));
//  ^ we dont want to deference array twice
    for (j = 0; j < 3; j++) {
        array[i][j] = malloc(3*sizeof(int));
    }
}

essayez ce qui suit:

    for (i = 0; i < 3; i++) {
        array[i] = malloc(3*sizeof(int*));
        for (j = 0; j < 3; j++) {
            array[i][j] = malloc(3*sizeof(int));
        }
    }
2
Malcolm McCullough

De cette manière, vous ne pouvez allouer qu’un seul bloc de mémoire et le tableau dynamique se comporte comme le statique (c’est-à-dire la même contiguïté mémoire) . Vous pouvez également libérer de la mémoire avec un seul tableau (libre) comme des tableaux 1D ordinaires.

double*** arr3dAlloc(const int ind1, const int ind2, const int ind3)
{
  int i;
  int j;
  double*** array = (double***) malloc( (ind1 * sizeof(double*)) + (ind1*ind2 * sizeof(double**)) + (ind1*ind2*ind3 * sizeof(double)) );
  for(i = 0; i < ind1; ++i) {
    array[i] = (double**)(array + ind1) + i * ind2;
    for(j = 0; j < ind2; ++j) {
      array[i][j] = (double*)(array + ind1 + ind1*ind2) + i*ind2*ind3 + j*ind3;
    }
  }
  return array;
}
2
Alberto

J'espère que ceci vous aidera!!!!

Lors de l'allocation de mémoire pour un tableau 2D à l'intérieur d'un tableau 3D, affectez la mémoire allouée au tableau [i] et non au * tableau [i]. Ceci fonctionnera sans erreur de segmentation.

Voici votre programme

int main () 
{
    int ***array = malloc(3*sizeof(int**));
    int i, j;

    for (i = 0; i < 3; i++) {
       array[i] = malloc(3*sizeof(int*));
       for (j = 0; j < 3; j++) {
          array[i][j] = malloc(3*sizeof(int));
       }
    }

    array[1][2][1] = 10;

    return 0;

}

1
Quref

Ci-dessous le code pour les allocations de mémoire 3D:

int row3d = 4;
int column3d = 4;
int height3d =4;
int val3d =10;

int ***arr3d = (int***)malloc (row3d*sizeof(int**));
for (int i =0 ; i<column3d;i++)
{
    arr3d[i] = (int**)malloc (column3d*sizeof(int*));
    for (int j = 0;j<height3d;j++)
    {
        arr3d[i][j] = (int*)malloc (height3d*sizeof(int));

        for (int z =0;z<height3d;z++,val3d++)
        {
            arr3d[i][j][z]   = val3d;
        }
    }

}
// De allocation.
for (int i=0;i<row3d;i++)
{
    for(int j=0;j<column3d;j++)
    {
        free(arr3d[i][j]);
    }
}
free(arr3d);
arr3d = 0;
1
ashutosh

Vous vous forcez à percevoir ceci comme deux manières fondamentalement différentes d'allouer un tableau 3D. Cette perception est renforcée par deux détails de différenciation définitifs: 1) la seconde méthode utilise plusieurs niveaux de indirection pour accéder aux éléments réels, 2) la seconde méthode alloue les tableaux 1D de niveau inférieur de manière indépendante .

Mais pourquoi insistez-vous pour allouer les tableaux 1D de niveau inférieur de manière indépendante ? Tu n'as pas à faire ça. Et une fois que vous en avez tenu compte, vous devez savoir qu’il existe une troisième méthode de construction de votre matrice 3D.

int ***array3d = malloc(3 * sizeof(int **));
int **array2d = malloc(3 * 3 * sizeof(int *));
int *array1d = malloc(3 * 3 * 3 * sizeof(int));

for (size_t i = 0; i < 3; i++) 
{
  array3d[i] = array2d + i * 3;
  for (size_t j = 0; j < 3; j++)
    array3d[i][j] = array1d + i * 3 * 3 + j * 3;
}

array[1][2][1] = 10;

Si vous examinez de près cette méthode d’allocation, vous constaterez qu’en fin de compte, c’est à peu près la même chose que votre deuxième méthode: elle construit une structure de tableau à trois niveaux en utilisant des pointeurs intermédiaires à chaque niveau d’indirection. La seule différence est qu’il pré-alloue au préalable la mémoire pour chaque niveau d’indirection indirectionnel, «en une fois», au lieu de faire plusieurs appels malloc répétitifs. Le cycle suivant répartit simplement cette mémoire préallouée entre les sous-tableaux (c’est-à-dire qu’il initialise simplement les pointeurs).

Cependant, si vous regardez encore plus près, vous remarquerez également que la mémoire réelle de l'élément de tableau (la ints qui stocke les valeurs réelles) est allouée de la même manière que dans votre première méthode: malloc(3 * 3 * 3 * sizeof(int)); - en tant que simple flat tableau contigu.

Maintenant, si vous y réfléchissez, vous devez comprendre que cette troisième méthode n’est pas très différente de la première. Ils utilisent tous deux un tableau plat de taille xSize * ySize * zSize pour stocker les données. La seule différence réelle ici est la méthode que nous utilisons pour calculer l’index d’accès à ces données non hiérarchiques. Dans la première méthode, nous calculions l’indice à la volée comme

array1d[z * ySize * xSize + y * xSize + x]

dans la troisième méthode, nous pré-calculons les pointeurs vers les éléments de tableau à l'avance , en utilisant essentiellement la même formule, stockons les résultats précalculés dans des tableaux supplémentaires et les récupérons ultérieurement à l'aide de la syntaxe d'accès "naturelle" au tableau

array3d[x][y][x]

La question ici est de savoir si ce calcul préalable vaut l’effort supplémentaire et la mémoire supplémentaire. La réponse est: généralement non, ce n'est pas. En dépensant cette mémoire supplémentaire, vous ne retirerez aucun avantage de performances appréciable (il est probable que cela ralentira votre code).

La seule situation dans laquelle votre deuxième méthode pourrait être utile est celle où vous utilisez réellement jagged/ragged array: tableau multidimensionnel fragmenté avec certaines parties de sous-tableaux manquantes/non utilisées ou de taille réduite. Par exemple, si certains sous-tableaux 1D ou 2D de votre tableau 3D sont connus pour ne contenir que des zéros, vous pouvez décider de ne pas les stocker du tout en mémoire et de définir les pointeurs correspondants sur NULL. Cela impliquerait l’utilisation de votre deuxième méthode, dans laquelle les sous-tableaux sont alloués (ou non) indépendamment. Si les données sont volumineuses, les économies de mémoire obtenues pourraient en valoir la peine.

Notez également que s’il s’agit de tableaux de 3 dimensions ou plus, les méthodes de répartition première/deuxième/troisième peuvent être utilisées ensemble, simultanément pour différents niveaux d’indirection. Vous pouvez décider de mettre en œuvre des tableaux 2D en utilisant la première méthode, puis de les combiner dans un tableau 3D en utilisant la deuxième méthode.

1
AnT
#include<stdio.h>
#include<stdlib.h>

#define MAXX 3
#define MAXY 4
#define MAXZ 5

main()
{
int ***p,i,j;
p=(int ***) malloc(MAXX * sizeof(int **));

for(i=0;i < MAXX;i++)
{
p[i]=(int **)malloc(MAXY * sizeof(int *));
for(j=0;j < MAXY;j++)
p[i][j]=(int *)malloc(MAXZ * sizeof(int));
}

for(k=0;k < MAXZ;k++)
for(i=0;i < MAXX;i++)
for(j=0;j < MAXY;j++)
p[i][j][k]= < something >;

}
0
Vivek Patel

ajoutez #include "stdlib.h" et supprimez le * de * tableau [i] et il fonctionnera une fois compilé dans gcc 4.4.1 sur Ubuntu

aussi, si vous ajoutez des instructions d'impression, vous pourrez trouver vos bugs plus rapidement

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

int main () {
  int ***array = malloc(3*sizeof(int**));
  int i, j;

  printf("%s\n","OK");

  for (i = 0; i < 3; i++) {
    printf("i = %i \n",i);
    array[i] = malloc(3*sizeof(int*));
    for (j = 0; j < 3; j++) {
      printf("i,j = %i,%i \n",i,j);
      array[i][j] = malloc(3*sizeof(int));
    }
  }

  array[1][2][1] = 10;

  return 0;
}
0
Paul