web-dev-qa-db-ja.com

C:多次元配列のメモリを正しく解放する

多次元配列を初期化する次のANSI Cコードがあるとします。

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

**aを使用した後、メモリから正しく解放するにはどうすればよいですか?


[Update](ソリューション)

Tim(およびその他)のおかげで answer になりました。このような関数を実行して、多次元配列からメモリを解放することができます。

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

わかりました。必要なfree()呼び出しの順序を正確に説明しているので、かなり混乱しています。そのため、人々が何をしようとしているのか、その理由を明らかにしようと思います。

基本から始めて、malloc()を使用して割り当てられたメモリを解放するには、free()によって指定されたポインタを使用してmalloc()を呼び出すだけです。したがって、このコードの場合:

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

あなたはマッチングが必要です:

_free(a);
_

この行の場合:

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

あなたはマッチングが必要です:

_free(a[i]);
_

同様のループ内。

これが複雑になるのは、これが発生する必要がある順序です。複数の異なるメモリチャンクを取得するためにmalloc()を数回呼び出す場合、一般に、それらを使い終わったときにfree()を呼び出す順序は関係ありません。ただし、非常に特定の理由により、ここでは順序が重要です。mallocedメモリの1つのチャンクを使用して、mallocedメモリの他のチャンクへのポインタを保持しています。 mustnotは、free()を使用してメモリを返した後、メモリの読み取りまたは書き込みを試行するため、これを解放する必要があります_a[i]_ beforeに格納されたポインタを持つチャンクは、aチャンク自体を解放します。 _a[i]_に格納されたポインタを持つ個々のチャンクは相互に依存しないため、好きな順序でfreedにすることができます。

これをすべてまとめると、次のようになります。

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

最後のヒント:malloc()を呼び出すときは、次の変更を検討してください。

_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])));
_

これは何をしているのですか?コンパイラは、aが_int **_であることを認識しているため、sizeof(*a)sizeof(int *)と同じであると判断できます。ただし、後で気が変わってcharsまたはshortsまたはlongsまたはintsの代わりに配列内のものを必要とする場合、またはこのコードを適合させます後で別の目的で使用するには、上記の最初の引用された行の残りの1つの参照のみをintに変更する必要があります。そうすると、他のすべてが自動的に配置されます。これにより、将来的に気付かれないエラーの可能性がなくなります。

幸運を!

67
Tim

割り当てたものを元に戻します:

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

これは、メモリを最初に割り当てたときのreverseの順序で行う必要があることに注意してください。最初にfree(a)を実行した場合、_a[i]_は解放後にメモリにアクセスしますが、これは未定義の動作です。

8
Greg Hewgill

もう一度配列を反復処理し、ポイントされたメモリのmallocと同じ数の解放を行ってから、ポインタの配列を解放する必要があります。

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

関数名を変更して、割り当て演算子を正確に逆の順序で記述すれば、大丈夫です。

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

  free(a);

もちろん、まったく同じ逆の順序で割り当てを解除する必要はありません。同じメモリの解放を1度だけ追跡し、割り当てられたメモリへのポインタを「忘れない」ようにする必要があります(aを最初に解放した場合のように)。しかし、逆の順序で割り当てを解除することは、後者に対処するための優れた役割です。

コメントの litb で指摘されているように、割り当て/割り当て解除に副作用がある場合(C++のnew/delete演算子のように)、割り当て解除の逆順でこの特定の例よりも重要です。

3
P Shved

私はmalloc()とfree()を一度だけ呼び出します:

#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