int a = 0;
int *b = malloc (sizeof(int));
b = malloc (sizeof(int));
上面的代码很糟糕,因为它在堆上分配内存然后不释放它,这意味着你失去了对它的访问权限。但是你也创建了'a'并且从未使用它,所以你也在堆栈上分配了内存,在范围结束之前不会释放它。
那么为什么不在堆上释放内存是不好的做法,但是不能释放堆栈中的内存(直到作用域结束)?
注意:我知道堆栈上的内存不能被释放,我想知道它为什么不被认为是坏的。
答案 0 :(得分:8)
当范围结束时,堆栈内存将自动释放。除非您明确释放,否则堆上分配的内存将保持占用状态。举个例子:
void foo(void) {
int a = 0;
void *b = malloc(1000);
}
for (int i=0; i<1000; i++) {
foo();
}
运行此代码会将可用内存减少b
所需的1000 * 1000字节,而当a
调用返回时,foo
所需的内存将始终自动释放
答案 1 :(得分:5)
简单:因为你会泄漏内存。内存泄漏很糟糕。泄漏:坏,免费:好。
当调用malloc
或calloc
,或者实际上任何* alloc函数时,你声称一块内存(其大小由传递给分配函数的参数定义)。
与堆栈变量不同,堆栈变量驻留在内存的一部分中,程序具有一定程度的自由统治,相同的规则不适用于堆内存。你可能需要分配堆内存有多种原因:堆栈不够大,你需要一个指针数组,但无法知道这个数组在编译时需要多大,你需要共享一些内存(线程噩梦),一个需要在程序中的各个地方(函数)设置成员的结构......
其中一些原因,就其性质而言,暗示只要指针到该内存超出范围,就无法释放内存。另一个指针可能仍然在另一个范围内,指向同一块内存 但是,正如其中一条评论中所提到的,这有一点点缺点:堆内存不仅需要对程序员部分有更多的认识,而且它也比在堆栈上工作更昂贵,更慢。 所以一些经验法则是:
反正
一些例子:
堆栈溢出:
#include <stdio.h>
int main()
{
int foo[2000000000];//stack overflow, array is too large!
return 0;
}
所以,在这里我们已经耗尽了堆栈,我们需要在堆上分配内存:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *foo= malloc(2000000000*sizeof(int));//heap is bigger
if (foo == NULL)
{
fprintf(stderr, "But not big enough\n");
}
free(foo);//free claimed memory
return 0;
}
或者,一个数组的例子,其长度取决于用户输入:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *arr = NULL;//null pointer
int arrLen;
scanf("%d", &arrLen);
arr = malloc(arrLen * sizeof(int));
if (arr == NULL)
{
fprintf(stderr, "Not enough heap-mem for %d ints\n", arrLen);
exit ( EXIT_FAILURE);
}
//do stuff
free(arr);
return 0;
}
所以列表继续...... malloc
或calloc
有用的另一种情况:字符串数组,大小各不相同。比较:
char str_array[20][100];
在这种情况下,str_array
是一个包含20个char数组(或字符串)的数组,每个数组长100个字符。但是如果100个字符你需要的最大怎么办呢?平均来说,你只会使用25个或更少的字符?
你是用C语言编写的,因为它很快,你的程序不会使用比实际需要更多的资源?那不是你真正想做的事情。更有可能的是,你想要:
char *str_array[20];
for (int i=0;i<20;++i) str_array[i] = malloc((someInt+i)*sizeof(int));
现在str_array
中的每个元素都具有我需要分配的内存量。那更干净。但是,在这种情况下,调用free(str_array)
不会削减它。另一个经验法则是:每个alloc调用都必须进行free
调用以匹配,因此解除分配此内存的行为如下所示:
for (i=0;i<20;++i) free(str_array[i]);
注意:
动态分配的内存不是内存泄漏的唯一原因。必须要说的是。如果您读取文件,使用fopen
打开文件指针,但未能关闭该文件(fclose
)也会导致泄漏:
int main()
{//LEAK!!
FILE *fp = fopen("some_file.txt", "w");
if (fp == NULL) exit(EXIT_FAILURE);
fwritef(fp, "%s\n", "I was written in a buggy program");
return 0;
}
编译并运行得很好,但它会包含一个泄漏,只需添加一行即可轻松插入(并且应该插入):
int main()
{//OK
FILE *fp = fopen("some_file.txt", "w");
if (fp == NULL) exit(EXIT_FAILURE);
fwritef(fp, "%s\n", "I was written in a bug-free(?) program");
fclose(fp);
return 0;
}
作为一个助手:如果范围很长,那么你很可能试图将太多的东西塞进单一的功能中。即便如此,如果你不是:你可以随时释放所声称的内存,它不一定是当前范围的结束:
_Bool some_long_f()
{
int *foo = malloc(2000000000*sizeof(int));
if (foo == NULL) exit(EXIT_FAILURE);
//do stuff with foo
free(foo);
//do more stuff
//and some more
//...
//and more
return true;
}
答案 2 :(得分:2)
因为 堆栈 和 堆 ,在其他答案中多次提及,有时会被误解的条款,甚至在C程序员中, Here is a great conversation 讨论该主题....
那么为什么不在堆上释放内存是不好的做法,但是没有释放堆栈上的内存(直到范围结束)?
堆栈上的内存(例如分配给自动变量的内存)将在退出创建它们的范围时自动释放。
scope
是指全局文件,函数还是函数内的块({...})
但是堆上的内存(例如使用malloc()
,calloc()
或甚至fopen()
创建的内存会分配内存资源,这些内存资源在您使用{明确表示释放它们之前不会被用于任何其他目的{1}}或free()
要 说明为什么在不释放内存的情况下分配内存是不好的做法 ,请考虑如果应用程序设计为自动运行很长时间会发生什么,比如说应用程序用于控制汽车巡航控制的PID回路。并且,在该应用程序中,存在未释放的内存,并且在运行3小时后,微处理器中可用的内存耗尽,导致PID突然发生故障。 “啊!”,你说,“这永远不会发生!”的 Yes, it does. (look here) 即可。 (不完全相同的问题,但你明白了)
如果该单词图片不起作用,那么请观察在您自己的PC上运行此应用程序(内存泄漏)时会发生什么。 (至少查看下面的图片,看看它对我做了什么)
在最终停止工作之前,您的计算机将表现出越来越迟缓的行为。可能,您将被要求重新启动以恢复正常行为 (我不建议运行它)
fclose()
运行此内存猪只需30秒后的内存使用情况:
答案 3 :(得分:0)
我想这与范围'结尾'经常(在函数的末尾)有关,这意味着如果从该函数返回创建a
并分配{{1}在某种意义上,您将{em>释放 b
占用的内存,并且a
尝试几次调用该功能,你很快就会耗尽所有记忆。堆栈变量不会发生这种情况(除了在有缺陷的递归的情况下)
答案 4 :(得分:0)
当剩下该功能时,通过重置帧指针,自动回收局部变量的内存。
答案 5 :(得分:0)
问题是,除非您明确释放它,否则在您的程序结束之前,您在堆永远上分配的内存将被释放。这意味着每次分配更多堆内存时,会越来越多地减少可用内存,直到最终程序耗尽(理论上)。
堆栈内存不同,因为它是按照编译器确定的可预测模式布局和使用的。它根据需要扩展给定块,然后在块结束时收缩。
答案 6 :(得分:0)
那么为什么不在堆上释放内存是不好的做法,但是不能释放堆栈中的内存(直到作用域结束)?
想象一下:
while ( some_condition() )
{
int x;
char *foo = malloc( sizeof *foo * N );
// do something interesting with x and foo
}
x
和foo
都是auto
(“堆叠”)变量。 逻辑上说话,在每个循环迭代 1 中创建并销毁的每个新实例;无论此循环运行多少次,程序将只为每个循环分配足够的内存。
但是,每次循环时,都会从堆中分配N个字节,并将这些字节的地址写入foo
。即使变量 foo
在循环结束时不再存在,那么堆内存仍然会被分配,现在你不能free
因为你丢失了参考它。因此,每次循环运行时,都会分配另外N个字节的堆内存。随着时间的推移,您的堆内存耗尽,这可能会导致代码崩溃,甚至导致内核崩溃,具体取决于平台。即使在此之前,您可能会在代码或在同一台计算机上运行的其他进程中看到性能下降。
对于像Web服务器这样长时间运行的进程,这致命。你总是想确保你自己清理干净。基于堆栈的变量会为您清理,但是您负责在完成后清理堆。
<小时/> 1。在实践中,这(通常)并非如此;如果查看生成的机器代码,您(通常)会在函数入口处看到为
x
和foo
分配的堆栈空间。通常,所有局部变量的空间(无论其在函数内的范围如何)都会立即分配。