正确释放多维数组的内存(C语言)

43

假设你有以下 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之后,如何正确释放内存?


[更新](解决方案)

感谢Tim(和其他人)的答案,我现在可以编写这样一个函数来释放我的多维数组中的内存:

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

2
术语争议:这不是C通常称为“多维数组”的方式。这只是使用语法“a[i][j]”的唯一方法,同时仍允许两个维度在编译时未知。另一种多维数组是数组的数组,而不是指向(数组第一个元素的)指针的数组。 - Steve Jessop
5个回答

77

好的,关于释放内存所需的free()调用的顺序存在一定的混淆,我将尝试澄清人们试图表达的内容以及原因。

首先从基础知识开始,要释放使用malloc()分配的内存,只需使用malloc()给出的指针来调用free()即可。因此,对于以下代码:

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

你需要匹配:

free(a);

对于这行代码:

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

你需要匹配:

free(a[i]);

在类似的循环内部。

问题在于这些操作需要按照一定的顺序完成。如果你多次调用malloc()来获取不同的内存块,在一般情况下,释放时调用free()的顺序并不重要。但是,在这里顺序非常重要原因是:你正在使用一个malloc分配的内存块来保存指向其他malloc分配的内存块的指针。由于你在使用完后必须将内存块通过free()返回,因此你必须先释放存储在a[i]中的指针所指向的内存块,然后才能释放a本身所占用的内存块。存储在a[i]中的指针所指向的各个内存块之间并没有依赖关系,因此可以以任意顺序进行释放操作。

因此,综合考虑所有因素,我们得到以下代码:

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 *) 相同。但是,如果你后来改变主意想要在数组中使用 charshortlong 或其他类型的值,或者你将此代码适用于其他用途,你只需更改上述第一行中仍保留的对 int 的引用,其余所有内容都会自动落入位。这消除了未来出现未被注意到的错误的可能性。

祝好运!


2
+1 优秀的答案;感谢您对“逆序”问题和执行sizeof(*a)的点进行解释。 - Andreas Grech
1
另外,如果我说 sizeof(*a[i]) 等同于你的 sizeof(*(a[i])),因为数组符号 [] 的优先级高于 *,请纠正我。 - Andreas Grech
1
不,我认为你是对的。http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence 然而,我的工作原则是,如果我必须查找它,那么阅读我的代码的其他人可能也必须查找它,因此为了节省他们(和我自己以后)的麻烦,明确地使用括号有助于澄清事情并节省时间。不过这只是个人偏好。 - Tim

9

撤销您分配的内容:

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

请注意,您必须按照与最初分配内存相反的顺序进行操作。如果您首先执行了free(a),那么a[i]将访问已被释放的内存,这是未定义的行为。请保留HTML标记。

2
说你必须按相反的顺序释放可能会误导。你只需要在指针本身之后释放指针数组即可。 - Andomar
1
这不是另一种说法吗,就是反转吗? - GManNickG
2
我认为@Andomar的意思是,释放a[i]的顺序并不重要,只要在释放a之前先释放它们所有。换句话说,你可以通过a[0]到a[m-1]或a[m-1]到a[0]或所有偶数a[]后跟奇数来释放它们。但我也确定@GregH并没有意味着你必须按相反的顺序处理a[],特别是考虑到他的代码。 - paxdiablo

4

您需要再次迭代数组,并为指向的内存执行与malloc一样多的释放,然后释放指针数组。

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

3

将你的分配运算符按完全相反的顺序编写,更改函数名称,你就会没问题了。

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

  free(a);

当然,您不必以完全相反的顺序进行解除分配。您只需要跟踪确切一次释放相同内存,并且不要“忘记”指向已分配内存的指针(如果首先释放了a,则会发生这种情况)。但是,按照相反的顺序进行取消分配是解决后者的好方法。
正如评论中litb所指出的那样,如果分配/取消分配具有副作用(例如C++中的new/delete运算符),则有时取消分配的后向顺序比在此特定示例中更重要。

因为a[1]是在a[0]之后分配的,所以您应该先释放a[1]。 - P Shved
谁说“你需要”了?我认为这只是看起来……合理。 - P Shved
2
由于在 C 语言中,free 函数不会对程序的代码产生副作用,所以似乎问题并不大。而“反向执行”的规则对于那些与 freealloc 相关联有副作用的语言更为重要,例如具有析构函数/构造函数的 C++。 - Johannes Schaub - litb
2
我相信大多数人会向前释放数组,而不是向后。 - Edan Maor
@Till:我不为自己的行为辩解,希望有些人在阅读代码时更加灵活。 - P Shved
显示剩余3条评论

1
我会只调用一次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;
}

这怎么回答问题了? - Blindy
Pavel Shved 写了正确的答案。我只是写了一条带有一些代码的评论。 - sambowry
1
你应该将注释写入问题的“评论”字段中。它还支持代码块。 - Johannes Schaub - litb
@litb:请将我的答案复制到问题的评论栏中。谢谢。 - sambowry
1
没有理由进行负评。我发现在SO上对许多问题的回答是“你不应该那样做,而应该这样做”。有机会回答问题的字面意思和实际意义。 - jmucchiello

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接