关于C#:当你在malloc之后没有自由时,真正发生了什么?

What REALLY happens when you don't free after malloc?

这件事已经困扰我很久了。

我们在学校都被教导(至少,我是这样)必须释放每一个分配的指针。不过,我有点好奇不释放记忆的真正代价。在某些明显的情况下,例如当在循环或线程执行的一部分内调用malloc时,释放非常重要,因此没有内存泄漏。但请考虑以下两个例子:

首先,如果我有这样的代码:

1
2
3
4
5
6
int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}

真正的结果是什么?我的想法是,这个过程会终止,然后堆空间无论如何都会消失,因此错过对free的调用不会有任何伤害(不过,我确实认识到无论如何都要拥有它以实现关闭、可维护性和良好实践的重要性)。我这样想对吗?

其次,假设我有一个程序,它的行为有点像shell。用户可以声明像aaa = 123这样的变量,这些变量存储在一些动态数据结构中,以供以后使用。显然,您可能会使用一些调用*alloc函数(hashmap、链表等)的解决方案。对于这种程序,调用malloc后再释放是没有意义的,因为这些变量在程序执行期间必须始终存在,而且没有好的方法(我可以看到)用静态分配的空间来实现这一点。有一堆已分配但仅作为进程结束的一部分释放的内存是不是很糟糕?如果是,还有什么选择?


几乎每个现代操作系统都会在程序退出后恢复所有分配的内存空间。我能想到的唯一例外可能是类似于Palm OS的情况,在这种情况下,程序的静态存储和运行时内存几乎是相同的,因此不释放可能会导致程序占用更多的存储空间。(我只是在推测。)

因此,一般来说,除了拥有比您需要的更多存储的运行时成本外,它没有任何危害。当然,在您给出的示例中,您希望保留一个变量的内存,该变量在清除之前可能会被使用。

然而,一旦您不再需要内存,它就被认为是一种很好的方式来释放内存,并且在程序退出时释放仍然存在的任何东西。这更像是一个练习,了解你在使用什么记忆,思考你是否还需要它。如果不跟踪,可能会发生内存泄漏。

另一方面,在退出时关闭文件的类似警告有一个更具体的结果——如果不这样做,则写入它们的数据可能不会被刷新,或者如果它们是临时文件,则完成后它们可能不会被删除。此外,数据库句柄应该提交它们的事务,然后在完成这些事务后关闭它们。类似地,如果你使用一个面向对象的语言,如C++或Objic C,当你完成一个对象时,不释放一个对象将意味着析构函数永远不会被调用,并且类负责的任何资源可能不会被清理干净。


是的,你是对的,你的例子没有任何危害(至少在大多数现代操作系统上没有)。一旦进程退出,操作系统将恢复进程分配的所有内存。

来源:分配和GC神话(PostScript警报!)

Allocation Myth 4: Non-garbage-collected programs
should always deallocate all memory
they allocate.

The Truth: Omitted
deallocations in frequently executed
code cause growing leaks. They are
rarely acceptable. but Programs that
retain most allocated memory until
program exit often perform better
without any intervening deallocation.
Malloc is much easier to implement if
there is no free.

In most cases, deallocating memory
just before program exit is pointless.
The OS will reclaim it anyway. Free
will touch and page in the dead
objects; the OS won't.

Consequence: Be careful with"leak
detectors" that count allocations.
Some"leaks" are good!

也就是说,你真的应该尽量避免所有的内存泄漏!

第二个问题:你的设计还可以。如果您需要在应用程序退出之前存储一些内容,那么可以使用动态内存分配来实现这一点。如果您事先不知道所需的大小,则不能使用静态分配的内存。


===未来的校对和代码重用怎么办?= =

如果您不编写释放对象的代码,那么您将把代码限制为仅在可以依赖于关闭进程释放的内存时才安全使用…即小型一次性项目或"扔掉"[1]项目……你知道过程何时结束。

如果您编写的代码释放了()所有动态分配的内存,那么您将对代码进行未来检查,并允许其他人在更大的项目中使用它。

[1]关于"扔掉"项目。"丢弃"项目中使用的代码有一种不会被丢弃的方式。下一件事你知道十年过去了,你的"丢弃"代码还在使用中)。

我听过一个故事,讲的是一个人为了好玩而写了一些代码,让他的硬件工作得更好。他说:"只是一种爱好,不会成为一个大而专业的人。"几年后,很多人都在使用他的"业余爱好"代码。


你是对的,没有造成伤害,只是离开会更快

原因有很多:

  • 所有桌面和服务器环境只需在exit()上释放整个内存空间。他们不知道程序内部的数据结构,比如堆。

  • 几乎所有的free()实现都不会向操作系统返回内存。

  • 更重要的是,在退出()之前就完成了,这是浪费时间。在退出时,只需释放内存页和交换空间。相反,一系列free()调用将占用CPU时间,并可能导致磁盘分页操作、缓存未命中和缓存收回。

关于未来代码重用的可能性,证明了无意义操作的确定性:这是一个考虑因素,但可以说它不是敏捷的方式。雅尼!


我通常在确定完成后释放每个分配的块。今天,我的程序的入口点可能是main(int argc, char *argv[]),但明天它可能是foo_entry_point(char **args, struct foo *f),并作为函数指针键入。

所以,如果发生这种情况,我现在有一个漏洞。

关于您的第二个问题,如果我的程序接受了a=5这样的输入,我将为a分配空间,或者在后面的a="foo"上重新分配相同的空间。这将一直分配到:

  • 用户键入了"unset a"
  • 我的清理功能被输入,要么服务于一个信号,要么用户键入"退出"
  • 我想不出任何现代操作系统在进程退出后不会回收内存。再说一次,free()很便宜,为什么不清理呢?正如其他人所说,像Valgrind这样的工具非常适合发现您真正需要担心的泄漏。尽管示例中的块会被标记为"仍然可以访问",但当您试图确保没有泄漏时,输出中会有额外的噪声。

    另一个神话是"如果它在main()中,我不必释放它",这是错误的。考虑以下内容:

    1
    2
    3
    4
    5
    6
    char *t;

    for (i=0; i < 255; i++) {
        t = strdup(foo->name);
        let_strtok_eat_away_at(t);
    }

    如果这发生在分叉/守护进程之前(理论上永远运行),那么您的程序刚刚泄漏了255次未确定的大小。

    一个好的、写得好的程序应该在它自己之后进行清理。释放所有内存、刷新所有文件、关闭所有描述符、取消所有临时文件的链接等。正常终止或接收到各种致命信号时,应达到此清理功能,除非您希望保留一些文件,以便检测崩溃并恢复。

    真的,善待可怜的灵魂,当你继续做其他事情的时候,他必须维护你的东西。交给他们"Valgrind Clean":)


    我完全不同意任何人谁说手术是正确的或没有伤害。

    每个人都在谈论现代和/或传统操作系统。

    但是如果我处在一个没有操作系统的环境中呢?哪里没有什么?

    想象一下,现在您正在使用线程样式的中断并分配内存。在C标准ISO/IEC:9899中,存储器的寿命规定为:

    7.20.3 Memory management functions

    1 The order and contiguity of storage allocated by successive calls to the calloc,
    malloc, and realloc functions is unspecified. The pointer returned if the allocation
    succeeds is suitably aligned so that it may be assigned to a pointer to any type of object
    and then used to access such an object or an array of such objects in the space allocated
    (until the space is explicitly deallocated). The lifetime of an allocated object extends
    from the allocation until the deallocation.[...]

    因此,不必考虑到环境正在为您做释放工作。否则,它将添加到最后一句:"或者直到程序终止。"

    所以换句话说:不释放内存不仅仅是一个坏习惯。它产生不可移植和不符合C的代码。它至少可以被视为"正确,如果环境支持以下内容:……]"。

    但如果你根本没有操作系统,就没有人替你做这项工作(我知道一般情况下,你不会在嵌入式系统上分配和重新分配内存,但有些情况下你可能会想这样做。)

    所以一般来说,用普通的C语言(作为操作的标记)。这只是产生错误的、不可移植的代码。


    退出时完全可以不释放内存;malloc()从名为"堆"的内存区域分配内存,当进程退出时释放整个进程堆。

    也就是说,人们仍然坚持认为在退出之前释放所有东西是好的一个原因是内存调试程序(例如,在Linux上的valgrind)在内存泄漏时检测未释放的块,如果您也有"真正的"内存泄漏,那么在最后得到"假"结果时发现它们会变得更加困难。


    这段代码通常可以正常工作,但要考虑代码重用的问题。

    您可能编写了一些代码片段,这些代码片段不能释放分配的内存,它是以这样的方式运行的,内存随后会自动回收。似乎没问题。

    然后其他人以每秒执行1000次的方式将您的代码片段复制到他的项目中。那个人现在在他的程序中有一个巨大的内存泄漏。一般来说不是很好,通常对服务器应用程序来说是致命的。

    代码重用在企业中很典型。通常情况下,公司拥有其员工生成的所有代码,每个部门都可以重用公司拥有的任何代码。因此,通过编写这种"看起来很无辜"的代码,你会给其他人带来潜在的头痛。这可能会让你被解雇。


    如果您使用的是已分配的内存,那么您没有做错任何事情。当您编写的函数(而不是主函数)在不释放内存的情况下分配内存,并且不将内存提供给程序的其余部分时,就会出现问题。然后,您的程序继续运行,并将内存分配给它,但无法使用它。您的程序和其他正在运行的程序将被剥夺该内存。

    编辑:不能100%准确地说其他正在运行的程序没有内存。操作系统总是可以让他们使用它,代价是将程序换出虚拟内存()。不过,关键是,如果您的程序释放了它不使用的内存,那么虚拟内存交换就不太可能是必需的。


    What's the real result here?

    你的程序泄露了内存。取决于您的操作系统,它可能已被恢复。

    大多数现代桌面操作系统都会在进程终止时恢复泄漏的内存,令人遗憾的是,忽略问题是很常见的,这里的许多其他答案都可以看到这一点。)

    但是您依赖的是一个不应该依赖的安全特性,并且您的程序(或函数)可能运行在一个系统上,下次这种行为确实会导致"硬"内存泄漏。

    您可能在内核模式下运行,或者在老式/嵌入式操作系统上运行,这些操作系统不采用内存保护作为折衷方案。(MMU占用了死区空间,内存保护需要额外的CPU周期,程序员要求自己清理也不算太多)。

    您可以随意使用和重新使用内存,但请确保在退出之前释放了所有资源。


    在OSTEP在线教科书的操作系统本科课程中,实际上有一节专门讨论你的问题。

    相关章节在第6页的内存API章节中"忘记释放内存",给出了以下解释:

    In some cases, it may seem like not calling free() is reasonable. For
    example, your program is short-lived, and will soon exit; in this case,
    when the process dies, the OS will clean up all of its allocated pages and
    thus no memory leak will take place per se. While this certainly"works"
    (see the aside on page 7), it is probably a bad habit to develop, so be wary
    of choosing such a strategy

    这段摘录是在介绍虚拟内存概念的背景下进行的。基本上,在书中的这一点上,作者解释说,操作系统的目标之一是"虚拟化内存",也就是说,让每个程序都相信它可以访问非常大的内存地址空间。

    在后台,操作系统会将用户看到的"虚拟地址"转换为指向物理内存的实际地址。

    但是,共享资源(如物理内存)需要操作系统跟踪正在使用它的进程。因此,如果一个进程终止了,那么它就在操作系统的能力和设计目标范围内,回收该进程的内存,以便它可以重新分布并与其他进程共享内存。

    编辑:下面复制摘录中提到的内容。

    ASIDE: WHY NO MEMORY IS LEAKED ONCE YOUR PROCESS EXITS

    When you write a short-lived program, you might allocate some space
    using malloc(). The program runs and is about to complete: is there
    need to call free() a bunch of times just before exiting? While it seems
    wrong not to, no memory will be"lost" in any real sense. The reason is
    simple: there are really two levels of memory management in the system.
    The first level of memory management is performed by the OS, which
    hands out memory to processes when they run, and takes it back when
    processes exit (or otherwise die). The second level of management
    is within each process, for example within the heap when you call
    malloc() and free(). Even if you fail to call free() (and thus leak
    memory in the heap), the operating system will reclaim all the memory of
    the process (including those pages for code, stack, and, as relevant here,
    heap) when the program is finished running. No matter what the state
    of your heap in your address space, the OS takes back all of those pages
    when the process dies, thus ensuring that no memory is lost despite the
    fact that you didn’t free it.

    Thus, for short-lived programs, leaking memory often does not cause any
    operational problems (though it may be considered poor form). When
    you write a long-running server (such as a web server or database management
    system, which never exit), leaked memory is a much bigger issue,
    and will eventually lead to a crash when the application runs out of
    memory. And of course, leaking memory is an even larger issue inside
    one particular program: the operating system itself. Showing us once
    again: those who write the kernel code have the toughest job of all...

    from Page 7 of Memory API chapter of

    Operating Systems: Three Easy Pieces
    Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau
    Arpaci-Dusseau Books
    March, 2015 (Version 0.90)

    < /块引用>


    不释放变量没有真正的危险,但是如果在不释放第一个块的情况下将指向内存块的指针分配给另一个内存块,则第一个块将不再可访问,但仍占用空间。这就是所谓的内存泄漏,如果您有规律地这样做,那么您的进程将开始消耗越来越多的内存,从其他进程中带走系统资源。

    如果进程是短期的,那么您通常可以避免这样做,因为当进程完成时,所有分配的内存都由操作系统回收,但是我建议您养成释放所有不再使用的内存的习惯。


    你在这方面是绝对正确的。在小的琐碎程序中,在程序死亡之前必须存在一个变量,因此重新分配内存并没有真正的好处。

    事实上,我曾经参与过一个项目,在这个项目中,程序的每一次执行都非常复杂,但是寿命相对较短,决定只是保持内存分配,而不是通过错误地取消分配来破坏项目的稳定性。

    也就是说,在大多数程序中,这不是一个真正的选项,否则会导致内存不足。


    您是正确的,当进程退出时,内存会自动释放。当进程终止时,有些人努力不进行大范围的清理,因为它将全部放弃给操作系统。但是,当程序运行时,应该释放未使用的内存。如果不这样做,最终可能会耗尽资源,或者在工作集太大时导致过度分页。


    如果你是从头开始开发一个应用程序,你可以在什么时候打免费电话上做出一些有根据的选择。您的示例程序很好:它分配内存,也许您让它工作几秒钟,然后关闭,释放它声明的所有资源。

    但是,如果您正在编写其他东西——一个服务器/长时间运行的应用程序,或者一个供其他人使用的库,那么您应该期望对您的malloc进行免费调用。

    暂时忽略务实的一面,遵循更严格的方法,强迫自己释放所有你的malloc会更安全。如果您不习惯在每次编码时都观察内存泄漏,那么您可以很容易地触发一些泄漏。换句话说,是的——你可以不带着它逃走;不过,请小心。


    如果程序在退出前忘记释放几个兆字节,操作系统将释放它们。但是如果你的程序一次运行几个星期,程序中的一个循环忘记在每次迭代中释放几个字节,你将有一个巨大的内存泄漏,它将耗尽你计算机中所有的可用内存,除非你定期重新启动它=>如果程序用于一个非常大的任务,即使是我,即使是很小的内存泄漏也可能是坏的。F它最初不是为一个而设计的。


    我认为你的两个例子实际上只有一个:free()应该只发生在进程的末尾,正如你指出的,这是无用的,因为进程正在终止。

    不过,在第二个示例中,唯一的区别是您允许一个未定义数量的malloc(),这可能导致内存不足。处理这种情况的唯一方法是检查malloc()的返回代码并采取相应的措施。