web-dev-qa-db-fra.com

C: Libérer correctement la mémoire d'un tableau multidimensionnel

Supposons que vous disposiez du code ANSI C suivant qui initialise un tableau multidimensionnel:

int main()
{
      int i, m = 5, n = 20;
      int **a = malloc(m * sizeof(int *));

      //Initialize the arrays
      for (i = 0; i < m; i++) { 
          a[i]=malloc(n * sizeof(int));
      }

      //...do something with arrays

      //How do I free the **a ?

      return 0;
}

Après avoir utilisé le **a, comment le libérer correctement de la mémoire?


[Mise à jour] (Solution)

Grâce à Tim (et aux autres) réponse , je peux maintenant faire une telle fonction pour libérer de la mémoire de mon tableau multidimensionnel:

void freeArray(int **a, int m) {
    int i;
    for (i = 0; i < m; ++i) {
        free(a[i]);
    }
    free(a);
}
38
Andreas Grech

OK, il y a beaucoup de confusion expliquant exactement dans quel ordre les appels nécessaires de free() doivent être, alors je vais essayer de clarifier ce que les gens essaient de faire et pourquoi.

En commençant par les bases, pour libérer de la mémoire qui a été allouée à l'aide de malloc(), vous appelez simplement free() avec exactement le pointeur qui vous a été donné par malloc(). Donc pour ce code:

int **a = malloc(m * sizeof(int *));

vous avez besoin d'une correspondance:

free(a);

et pour cette ligne:

a[i]=malloc(n * sizeof(int));

vous avez besoin d'une correspondance:

free(a[i]);

à l'intérieur d'une boucle similaire.

Là où cela se complique, c'est l'ordre dans lequel cela doit se produire. Si vous appelez malloc() plusieurs fois pour obtenir plusieurs morceaux de mémoire différents, en général, peu importe l'ordre dans lequel vous appelez free() lorsque vous en aurez terminé. Cependant, l'ordre est important ici pour une raison très spécifique: vous utilisez un bloc de mémoire malloced pour stocker les pointeurs vers d'autres blocs de mémoire malloced. Parce que vous devezpas tenter de lire ou d'écrire de la mémoire une fois que vous l'avez rendue avec free(), cela signifie que vous allez devoir libérer le morceaux avec leurs pointeurs stockés dans a[i]avant vous libérez le morceau a lui-même. Les morceaux individuels avec des pointeurs stockés dans a[i] Ne dépendent pas les uns des autres et peuvent donc être freed dans l'ordre de votre choix.

Donc, en rassemblant tout cela, nous obtenons ceci:

for (i = 0; i < m; i++) { 
  free(a[i]);
}
free(a);

Un dernier conseil: lorsque vous appelez malloc(), pensez à les modifier:

int **a = malloc(m * sizeof(int *));

a[i]=malloc(n * sizeof(int));

à:

int **a = malloc(m * sizeof(*a));

a[i]=malloc(n * sizeof(*(a[i])));

Qu'est-ce que ça fait? Le compilateur sait que a est un int **, Il peut donc déterminer que sizeof(*a) est identique à sizeof(int *). Cependant, si plus tard vous changez d'avis et voulez chars ou shorts ou longs ou quoi que ce soit dans votre tableau au lieu de ints, ou vous adaptez ce code pour une utilisation ultérieure dans autre chose, vous devrez modifier uniquement la référence restante à int dans la première ligne citée ci-dessus, et tout le reste se mettra automatiquement en place pour vous. Cela élimine la probabilité d'erreurs inaperçues à l'avenir.

Bonne chance!

67
Tim

Annuler exactement ce que vous avez alloué:

  for (i = 0; i < m; i++) { 
      free(a[i]);
  }
  free(a);

Notez que vous devez le faire dans l'ordre inverse à partir duquel vous avez initialement alloué la mémoire. Si vous avez fait free(a) en premier, alors a[i] Accèderait à la mémoire après sa libération, ce qui est un comportement indéfini.

8
Greg Hewgill

Vous devez réitérer le tableau et effectuer autant de libérations que de mallocs pour la mémoire pointée, puis libérer le tableau de pointeurs.

for (i = 0; i < m; i++) { 
      free (a[i]);
}
free (a);
4
Arkaitz Jimenez

Écrivez vos opérateurs d'allocation dans un ordre exactement inversé, en changeant les noms des fonctions, et tout ira bien.

  //Free the arrays
  for (i = m-1; i >= 0; i--) { 
      free(a[i]);
  }

  free(a);

Bien sûr, vous n'avez pas à désallouer dans le même ordre inversé. Il vous suffit de garder une trace de la libération de la même mémoire exactement une fois et de ne pas "oublier" les pointeurs vers la mémoire allouée (comme cela aurait été le cas si vous aviez libéré le a en premier). Mais la désallocation dans l'ordre inverse est un bon rôle de pouce pour aborder ce dernier.

Comme indiqué par litb dans les commentaires, si l'allocation/désallocation avait des effets secondaires (comme les opérateurs new/delete en C++), parfois l'ordre inverse de la désallocation serait être plus important que dans cet exemple particulier.

3
P Shved

Je n'appellerais malloc () et free () qu'une seule fois:

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

int main(void){
  int i, m = 5, n = 20;
  int **a = malloc( m*(sizeof(int*) + n*sizeof(int)) );

  //Initialize the arrays
  for( a[0]=(int*)a+m, i=1; i<m; i++ ) a[i]=a[i-1]+n;

  //...do something with arrays

  //How do I free the **a ?
  free(a);

  return 0;
}
1
sambowry