C: prawidłowe uwolnienie pamięci wielowymiarowej tablicy

Załóżmy, że masz następujący kod ANSI C, który inicjalizuje tablicę wielowymiarową:

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;
}

Po użyciu **a, Jak poprawnie uwolnić go z pamięci ?


[Update] (Solution)

Dzięki odpowiedzi Tima (i innych) , mogę teraz wykonać taką funkcję, aby zwolnić pamięć z mojej tablicy wielowymiarowej:

void freeArray(int **a, int m) {
    int i;
    for (i = 0; i < m; ++i) {
        free(a[i]);
    }
    free(a);
}
Author: Community, 2009-11-14

5 answers

OK, jest sporo zamieszania wyjaśniającego dokładnie, jaka kolejność konieczne połączenia muszą być w, więc postaram się wyjaśnić, co ludzie próbują dostać się do i dlaczego.

Zaczynając od podstaw, aby zwolnić pamięć, która została przydzielona używając malloc(), po prostu wywołujesz free() dokładnie ze wskaźnikiem które otrzymałeś od malloc(). Więc dla tego kodu:

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

Potrzebujesz dopasowania:

free(a);

I dla tej linii:

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

Potrzebujesz dopasowanie:

free(a[i]);

Wewnątrz podobnej pętli.

Gdzie to się komplikuje, jest kolejność, w jakiej to musi się wydarzyć. Jeśli dzwonisz malloc() kilka razy, aby uzyskać kilka różnych kawałków pamięci, w ogóle nie ma znaczenia, jaką kolejność nazwiesz free(), gdy skończyłeś z nimi. Jednak porządek jest tu ważny dla bardzo konkretny powód: używasz jednego kawałka mallocpamięci ed do przechowywania wskaźniki do innych fragmentów mallocpamięci ed. Bo ty musi Nie spróbuj odczytać lub zapisać pamięć po oddaniu jej z free(), oznacza to, że będziesz musiał uwolnić kawałki z ich wskaźniki przechowywane w a[i] przed uwolnisz a sam kawałek. Poszczególne fragmenty ze wskaźnikami przechowywane w a[i] nie zależą od każdego inne, a więc może być freed w dowolnej kolejności.

Więc, łącząc to wszystko razem, otrzymujemy to:

for (i = 0; i < m; i++) { 
  free(a[i]);
}
free(a);
[[30]}ostatnia wskazówka: podczas wywoływania malloc(), rozważ zmianę tych:
int **a = malloc(m * sizeof(int *));

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

Do:

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

a[i]=malloc(n * sizeof(*(a[i])));
Co to robi? Kompilator wie, że a jest int **, więc może Oblicz, że sizeof(*a) jest tym samym co sizeof(int *). Jednakże, jeśli później zmienisz zdanie i chcesz char s lub short s lub long S lub cokolwiek w tablicy zamiast int s, albo dostosujesz ten kod na później użyj w czymś innym, będziesz musiał zmienić tylko jeden pozostały odniesienie do int w pierwszej cytowanej linijce powyżej, a wszystko else automatycznie przypadnie Ci na miejsce. Usuwa to prawdopodobieństwo o niezauważonych błędach w przyszłości. Powodzenia!
 60
Author: Tim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-14 10:59:45

Cofnij dokładnie to, co przydzieliłeś:

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

Zauważ, że musisz to zrobić w odwrotnej kolejności, z której pierwotnie przydzielono pamięć. Jeśli najpierw wykonałeś free(a), to a[i] uzyskałbyś dostęp do pamięci po jej zwolnieniu, co jest niezdefiniowanym zachowaniem.

 8
Author: Greg Hewgill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-14 10:16:51

Musisz ponownie iterować tablicę i wykonać tyle zwolnień co mallocs dla wskazanej pamięci, a następnie zwolnić tablicę wskaźników.

for (i = 0; i < m; i++) { 
      free (a[i]);
}
free (a);
 4
Author: Arkaitz Jimenez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-14 10:23:27

Napisz operatory alokacji w dokładnie odwróconej kolejności, zmieniając nazwy funkcji, a wszystko będzie dobrze.

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

  free(a);

Oczywiście, nie musiszdeallokować w tej samej odwróconej kolejności. Po prostu musisz śledzić zwolnienie tej samej pamięci dokładnie raz i nie "zapominać" wskaźników do przydzielonej pamięci(tak jak byłoby, gdybyś zwolnił a pierwszy). Ale dealokacja w odwrotnej kolejności jest dobrą rolą kciuka, aby zająć się tym ostatnim.

Jak wskazano by litb in the comments, if allocation/deallocation had side-effects (like new/delete operatory w C++), czasami odwrotny porządek dealokacji byłby ważniejszy niż w tym konkretnym przykładzie.

 4
Author: P Shved,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:31

Wywołałbym malloc() i free () tylko raz:

#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
Author: sambowry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-14 10:40:08