为什么在指针上必须使用free()函数而不是普通声明?

13

当我声明一个指针时,为什么我必须使用free()函数:

int *temp = (int*)malloc(sizeof(int))
*temp = 3;

但是当我这样做时不行:

int temp = 3;
8个回答

33

普通的声明放在栈上。当函数返回时,堆栈指针恢复到调用函数之前的值,因此内存会自动释放。

基于malloc的声明从“堆”中分配,这要求程序员管理分配和释放。


1
这个也适用于全局声明的变量吗? - samoz
1
不,就我所知道的,在全局声明变量时,它们会被分配到“数据段”中——这是操作系统根据应用程序已知的存储需求而设置的单独的内存块。 - Alnitak
全局变量在包含它们的模块被卸载或进程终止时会被释放。 - Michael
我很好奇是否有一种实现方式,实际上并没有使用栈。 - Crashworks
@Crashworks:PIC18系列。它们有一个单一的SRAM内存区域;当然,其中的部分可以被视为“堆”或“栈”,并且可以像这样访问。它们还有EEPROM和指令内存,但它们位于不同的总线上。 - aib
显示剩余4条评论

12

你不必总是在指针上使用 free,只需在使用 malloc 分配内存的指针上使用。你可以声明一个指向堆栈上的内存位置的指针。

int a = 3;
int* p = &a;

内存和指针在超出范围后将被自动清除。使用malloc在堆上分配相同的内存,因此您必须手动处理清理。


只是为了澄清:指针变量在堆栈上创建,并且它们保存指向在堆上分配的数据的内存地址。在32位系统上,它们在堆栈上占用4个字节,在64位系统上占用8个字节。 - Brian R. Bondy
@Brian:感谢澄清。我认为我的编辑使我的答案在技术上更准确了。 - Bill the Lizard

8

因为这种语言允许你在堆栈和堆之间选择。

选择堆栈和堆的原因:

  • 堆上的变量故意不自我释放,以便您可以在代码块或函数的范围之外使用它们。
  • 与堆相比,使用堆栈更加高效。
  • 在大多数编译器上,您无法在运行时选择堆栈上对象或数组的大小,因此在这里将使用堆。

为什么不能自动释放堆:

因为没有办法知道何时完成内存使用。有一些方法可以模拟垃圾回收,但这需要在堆栈和堆上没有指向堆上数据的指针的情况下进行。

关于堆栈和堆的更多信息:

C语言允许您选择在堆栈或堆上定义变量。

  • 堆栈上的变量在作用域结束时自动释放。
  • 堆上的变量不会自动释放。

malloc 在堆上创建变量。简单的声明,如 int x; 在栈上创建变量。

在这里查看关于堆栈的更多阅读

指针:

为了澄清:指针变量在栈上创建,并且它们保存分配在堆上的数据的内存地址。它们在32位系统上占用4个字节,在64位系统上占用8个字节。


仅出于兴趣,因为我从未查看过 - 编译器是否必须每次进入代码块时创建一个新的堆栈帧,并在该块结束时恢复堆栈指针? - Alnitak
@Alnitak:我猜这取决于优化级别 - 记住,编译器甚至可能会内联您的代码并完全消除函数调用! - Christoph
我更多地考虑函数内的代码块,而不是函数调用。 - Alnitak
我认为它总是完成了Alnitak,但它可以被优化掉。 - Brian R. Bondy
@Alnitak:至少对于gcc,据我所知,堆栈帧仅在函数进入时创建(即push ebpmov ebp,espsub esp,SIZEOF_LOCAL_VARS),块的创建不会移动堆栈指针;我可能错了,所以请谨慎对待 ;) - Christoph

5
需要翻译的内容如下:

需要注意的是,C语言没有栈或堆的概念,尽管前面的答案99%的情况下是正确的并且提供了很好的见解。

C语言为对象定义了三种存储期:静态、自动和分配的。(§6.2.4.1)

静态对象(例如全局变量)在整个程序的持续时间内都可用。

自动对象存在于其变量处于作用域时。一旦变量超出作用域,它们就会停止存在。

需要注意的是,这两者是极端情况。 C语言为您提供了一个折中点:已分配的对象。 (搜索术语将是动态分配的内存。)对于这些对象,告诉计算机对象何时开始和结束它们的存在。这是通过使用标准函数malloc()(或衍生函数)和free()来完成的。

严格来说,您不必调用free()。或许您需要(您必须阅读标准以获得权威的观点),但您可以在main()结束之前全部执行。或者,交给操作系统替您执行(大多数操作系统都这样做)。但这又是一个极端情况--对象在调用malloc()时进入,程序终止时退出。

我不必详细讨论这里的实际意义:内存是有限的。您只能分配有限的字节数,然后就会耗尽内存。对于全部使用静态对象来说会过于浪费;试图重用静态内存块或一大块内存将是困难的,并且无论如何都类似于动态分配方法。对于长寿命对象使用自动存储将迫使您将它们的范围尽可能大,大致相当于静态对象的范围。

--

现在,一些注意事项:

{
    int *temp = malloc(sizeof(int));
    *temp = 5;
    //free(temp);
}

请注意,这里的temp是自动对象。它只在其作用域内存在,该作用域以}结束。然而,它指向的对象是已分配的。直到您调用其地址上的free(),它才会存在。由于temp包含该地址的唯一副本,一旦temp超出作用域,您将失去调用free()的机会。一些内存将永久分配,但不可用。这被称为内存泄漏垃圾回收是管理对象存储的另一种方法。C中的实现可能如下所示:
{
    int *temp = gc_malloc(sizeof(int));
    *temp = 5;
}

在这里,计算机会判断对分配的对象的最后一个引用temp是否失效,并且释放该对象可能是个好主意。

这是一种权衡,在这种情况下,您不必担心释放对象(这并不像简单的例子所表现的那样微不足道),但是gc_malloc()比简单的malloc()更复杂,并且在temp超出作用域的}处有不可见的代码执行。而计算机如何确定temp是最后一个引用,则是一个完全不同的话题。(一些实际解决方案可能需要您编写更多关于“int *temp”的代码。)


3
Alnitak是正确的。我想指出“在stack上”的真正含义。
当程序进行函数调用时,它期望函数完成一些工作,然后返回并继续执行下一行代码。函数无法知道在函数完成时应该返回到哪里。因此,机器为每个程序都有一个调用堆栈,用于在调用函数之前将程序中以下语句的地址推入其中。 "return"语句只是弹出程序地址并跳转到它。
堆栈也是一个方便的临时空间刮板。可以写入堆栈未使用的区域。在C函数内部声明局部变量就是这样做的。当函数返回时,不需要清理,释放或以其他方式处理堆栈,因为它只是一个临时空间,并且现在超出了范围。
相比之下,调用malloc()从堆中分配内存,该内存明确为程序设置内存,并在程序运行时保持在范围内。因此,如果您不free()内存,则将保留已分配的内存,并被视为内存泄漏。

2
需要调用free()并不取决于您是否声明了指针,而是取决于您是否使用malloc()分配了内存。
就像Brian Bondy之前所说,变量(如"int number"、"char string[10]"、"float your_boat"等)在超出范围时会消失,比如当您的代码离开函数块时。因此,在您的问题中的指针("temp")在调用free()时并不会消失,而是在调用malloc()时分配的任何内容都会消失。您的指针仍然保留在那里,也就是说,在您的示例代码之后,您可以说"temp = &some_other_variable",而无需再次声明"int *temp;"。
如果有人曾经实现过一个函数,他们同时也调用了malloc(),为您的程序声明了内存,并且这个函数不需要您释放该数据,那么您就可以说:
int * temp = (int*)malloc(sizeof(int));

没有之后的话

free(temp);

但这并不是 malloc() 的实现方式。


0

关于之前提到的问题,这篇文章 进一步澄清了事情。


-9

这是一个非常好的问题,虽然很多人会回答它是堆栈分配之间的区别,但根本的答案是底层系统向您公开了一些不应该公开的东西。

当您分配内存时,您不应该担心将其归还。系统应该足够聪明,能够发现您不再访问(指针或引用)它,因此可以自动收回内存。

像Java和C#这样的新语言已经做到了这一点。


这是有争议的,没有回答问题。它也与语言的年龄无关(例如 Lisp 是一种具有垃圾收集功能的古老语言)。 - rmeador
你正在做出一个重大的假设。例如,系统程序员绝对应该关注内存分配和释放。 - spoulson
即使在像Java和C#这样的语言中,你也必须担心归还内存。只是你需要担心的问题有所改变。而且,是的,答案大多数时候都是无意义的,就像一个解压并比较的竞赛一样。 - Torlack
我希望人们不仅能理解当前问题的解决方案,而且我想告诉人们为什么首先会出现这个问题的根本原因。我试图教授未被揭示的知识。我正在努力让人们理解和思考。 - Pyrolistical
我正在为编程系统进行开发,因此垃圾回收不是一个选项。 - samoz
显示剩余5条评论

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