3
int a = 0; 
int *b = malloc (sizeof(int));
b = malloc (sizeof(int));

上面的代码很糟糕,因为它在堆上分配内存然后不释放它,这意味着您无法访问它。但是您还创建了“a”并且从未使用过它,因此您还在堆栈上分配了内存,直到作用域结束才释放。

那么为什么不释放堆上的内存但不释放堆栈上的内存(直到作用域结束)是不好的做法呢?

注意:我知道堆栈上的内存无法释放,我想知道为什么它不被认为是坏的。

4

7 回答 7

8

作用域结束时,堆栈内存将自动释放。除非您明确释放它,否则在堆上分配的内存将保持占用状态。举个例子:

void foo(void) {
    int a = 0;
    void *b = malloc(1000);
}

for (int i=0; i<1000; i++) {
    foo();
}

运行此代码将使 所需的可用内存减少 1000*1000 字节b,而当您从调用a返回时,所需的内存将始终自动释放。foo

于 2013-11-05T15:32:34.730 回答
5

很简单:因为你会泄漏内存。内存泄漏很糟糕。泄漏:坏,免费:好。
当调用mallocorcalloc或任何 *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;
}

所以这个列表还在继续......另一个有用的情况malloccalloc一个字符串数组,所有的大小都可能不同。比较:

char str_array[20][100];

在本例str_array中是一个由 20 个字符数组(或字符串)组成的数组,每个 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;
}
于 2013-11-05T15:41:32.483 回答
2

因为在其他答案中多次提到的stackheap有时是被误解的术语,即使在 C 程序员中也是如此,这是讨论该主题的精彩对话......

那么为什么不释放堆上的内存但不释放堆栈上的内存(直到作用域结束)是不好的做法呢?

堆栈上的内存,例如分配给自动变量的内存,将在退出创建它们的范围时自动释放。是否scope表示全局文件、函数或函数内的块({...})。
但是堆上的内存,例如使用malloc(),创建的内存calloc(),甚至fopen()分配的内存资源在您明确使用free(), 或fclose()

为了说明为什么在不释放内存的情况下分配内存是不好的做法,请考虑如果应用程序被设计为自主运行很长时间会发生什么,假设该应用程序用于控制汽车巡航控制的 PID 循环中。并且,在该应用程序中,内存未释放,运行 3 小时后,微处理器中的可用内存耗尽,导致 PID 突然出现异常。“啊!”,你说,“这永远不会发生!” 是的,它确实。(看这里)。(不完全相同的问题,但你明白了)

如果那个单词图片不起作用,那么观察当你在你自己的 PC 上运行这个应用程序(内存泄漏)时会发生什么。(至少查看下图以了解它对我的作用)

您的计算机将表现出越来越缓慢的行为,直到它最终停止工作。您可能需要重新启动才能恢复正常行为。
(我不建议运行它)

#include <ansi_c.h>
char *buf=0;

int main(void)
{
    long long i;
    char text[]="a;lskdddddddd;js;'";

    buf = malloc(1000000);

    strcat(buf, "a;lskdddddddd;js;dlkag;lkjsda;gkl;sdfja;klagj;aglkjaf;d");
    i=1;    
    while(strlen(buf) < i*1000000)
    {
        strcat(buf,text); 
        if(strlen(buf) > (i*10000) -10)
        {
            i++;
            buf = realloc(buf, 10000000*i); 
        }
    }
    return 0;   
}  

运行此内存猪仅 30 秒后的内存使用情况:

在此处输入图像描述

于 2013-11-05T16:11:59.160 回答
0

我想这与范围'结束'经常(在函数的末尾)有关,这意味着如果你从创建a和分配的函数返回b,你将在某种意义上释放被占用的内存a,并在剩下的时间里丢失使用的执行内存b

尝试多次调用该函数,您很快就会耗尽所有内存。堆栈变量永远不会发生这种情况(除非递归有缺陷)

于 2013-11-05T15:33:03.340 回答
0

当函数离开时(通过重置帧指针),会自动回收局部变量的内存。

于 2013-11-05T15:33:16.250 回答
0

问题是你在堆上分配的内存在你的程序结束之前永远不会被释放,除非你明确地释放它。这意味着每次分配更多堆内存时,都会越来越多地减少可用内存,直到最终程序耗尽(理论上)。

堆栈内存是不同的,因为它的布局和使用由编译器确定的可预测模式。它根据给定块的需要扩展,然后在块结束时收缩。

于 2013-11-05T15:38:32.923 回答
0

那么为什么不释放堆上的内存但不释放堆栈上的内存(直到作用域结束)是不好的做法呢?

想象一下:

while ( some_condition() )
{
  int x;
  char *foo = malloc( sizeof *foo * N );

  // do something interesting with x and foo
}

x和都是(“堆栈”)变量 foo从逻辑上讲,每个循环迭代中都会创建和销毁每个新实例1;无论此循环运行多少次,程序都只会为每个循环的单个实例分配足够的内存。 auto

但是,每次循环时,都会从堆中分配 N 个字节,并将这些字节的地址写入foo. 即使变量 foo在循环结束时不再存在,堆内存仍然被分配,现在你不能free这样做,因为你失去了对它的引用。所以每次循环运行时,都会分配另外 N 字节的堆内存。随着时间的推移,堆内存会耗尽,这可能会导致代码崩溃,甚至会导致内核崩溃,具体取决于平台。甚至在此之前,您可能会看到代码或在同一台机器上运行的其他进程的性能下降。

对于像 Web 服务器这样长时间运行的进程,这是致命的。你总是想确保你自己清理干净。基于堆栈的变量为您清理,但您负责在完成后清理堆。


1. 在实践中,(通常)情况并非如此;如果您查看生成的机器代码,您(通常)会看到为函数入口分配的堆栈空间xfoo在函数入口处分配的堆栈空间。通常,所有局部变量的空间(无论它们在函数内的范围如何)都是一次分配的。

于 2013-11-05T16:12:12.227 回答