首页 文章

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

提问于
浏览
458

这已成为困扰我多年的事情 .

我们都在学校(至少,我是)教过你必须释放所有分配的指针 . 不过,我有点好奇,关于不释放内存的实际成本 . 在一些明显的情况下,比如在循环或线程执行的一部分中调用 malloc 时,释放是非常重要的,因此没有内存泄漏 . 但请考虑以下两个例子:

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

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

什么's the real result here? My thinking is that the process dies and then the heap space is gone anyway so there'在错过 free 的呼吁方面没有什么害处(但是,我确实认识到无论如何都需要关闭,可维护性和良好实践) . 我这个想法是对的吗?

其次,假设我的程序有点像shell . 用户可以声明像 aaa = 123 这样的变量,这些变量存储在一些动态数据结构中供以后使用 . 很明显,在调用 malloc 之后,你似乎有意义地永远自由,因为这些变量必须始终存在于程序期间's execution and there's没有好办法(我可以看到)用静态分配的空间来实现它 . 拥有一堆内存是不是很糟糕的设计_586222?

17 回答

  • 96

    我认为你的两个例子实际上只有一个: free() 应该只在进程结束时发生,正如你所指出的那样,因为进程终止所以没用 .

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

  • 11

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

    所以一般来说,它没有任何害处,除了拥有比你需要的更多存储空间的运行时成本 . 当然,在您给出的示例中,您希望为可能被使用的变量保留内存,直到它被清除 .

    然而,只要你不再需要它就可以释放内存,并且在程序退出时释放你仍然拥有的任何东西 . 这更像是一种了解你正在使用的记忆,并思考你是否仍然需要它的练习 . 如果您不跟踪,可能会有内存泄漏 .

    另一方面,在退出时关闭文件的类似警告会产生更具体的结果 - 如果不这样做,您写入的数据可能无法刷新,或者如果它们是临时文件,则可能不会完成后删除 . 此外,数据库句柄应提交其事务,然后在完成它们时关闭 . 类似地,如果您使用的是面向对象的语言(如C或Objective C),那么在完成对象时不释放对象将意味着析构函数永远不会被调用,并且类负责的任何资源都可能无法清除 .

  • 46

    是的你是对的,你的例子不会造成任何伤害(至少在大多数现代操作系统上都没有) . 进程退出后,操作系统将恢复进程分配的所有内存 .

    来源:Allocation and GC Myths(PostScript警报!)

    分配误区4:非垃圾收集程序应该始终释放它们分配的所有内存 . 真相:经常执行的代码中省略的解除分配导致泄漏增加 . 它们很少被接受 . 但是在程序退出之前保留最多分配内存的程序通常执行得更好而没有任何干预重新分配 . 如果没有免费的话,Malloc更容易实现 . 在大多数情况下,在程序退出之前释放内存是没有意义的 . 操作系统无论如何都会收回它 . 自由将触及死亡物体中的页面;操作系统不会 . 结果:小心计算分配的“泄漏检测器” . 一些“泄漏”是好的!

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

    第二个问题:你的设计还可以 . 如果您需要存储某些内容,直到您的应用程序退出,那么可以通过动态内存分配来执行此操作 . 如果您事先不知道所需的大小,则无法使用静态分配的内存 .

  • 20

    ===那么 future proofingcode reuse 怎么样? ===

    如果你 don't 编写代码来释放对象,那么你就是限制代码只有在你可以依赖被关闭的进程释放的内存时才能安全使用...即小的一次性使用项目或"throw-away" [1] projects)...你知道什么时候会结束这个过程 .

    如果你 do 编写了free()s所有动态分配的内存的代码,那么你将来会证明代码并允许其他人在更大的项目中使用它 .


    [1]关于“丢弃”项目 . “扔掉”项目中使用的代码有一种不被丢弃的方式 . 接下来你知道十年过去了,你的“扔掉”代码仍在使用中 .

    我听到一个关于一些人编写一些代码的故事,只是为了让他的硬件更好地工作 . 他说“只是一个爱好,不会大而专业” . 多年以后,很多人都在使用他的“爱好”代码 .

  • 2

    你是对的,没有造成伤害,退出的速度更快

    有各种原因:

    • 所有桌面和服务器环境都只是在exit()上释放整个内存空间 . 他们不了解程序内部数据结构,如堆 .

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

    • 更重要的是,在exit()之前完成时浪费时间 . 退出时,简单地释放内存页面和交换空间 . 相比之下,一系列free()调用将消耗CPU时间,并可能导致磁盘分页操作,缓存未命中和缓存驱逐 .

    关于未来代码重用的可能性,证明无意义操作的确定性:'s a consideration but it'可能不是Agile方式 . YAGNI!

  • 2

    一旦我完成了它,我通常会释放每个已分配的块 . 今天,我的程序的入口点可能是 main(int argc, char *argv[]) ,但明天它可能是 foo_entry_point(char **args, struct foo *f) 并输入为函数指针 .

    所以,如果发生这种情况,我现在有泄漏 .

    关于你的第二个问题,如果我的程序输入a = 5,我会为a分配空间,或者在随后的a =“foo”上重新分配相同的空间 . 这将保持分配,直到:

    • 用户输入'unset a'

    • 输入了我的清理功能,要么为信号提供服务,要么输入用户'quit'

    我想不出任何现代操作系统在进程退出后不回收内存 . 然后,free()便宜,为什么不清理?正如其他人所说的那样,像valgrind这样的工具非常适合发现你确实需要担心的漏洞 . 即使您示例的块标记为'still reachable',当您尝试确保没有泄漏时,它在输出中只是额外的噪音 .

    另一个神话是“如果它在main()中,我不必释放它”,这是不正确的 . 考虑以下:

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

    如果在分叉/守护之前(理论上永远运行),你的程序刚刚泄漏了不确定大小的t 255次 .

    一个好的,写得很好的程序应该始终清理干净 . 释放所有内存,刷新所有文件,关闭所有描述符,取消所有临时文件的链接等 . 这个清理功能应该在正常终止时或在收到各种致命信号时到达,除非你想留下一些文件,这样你就可以检测到崩溃并恢复 .

    真的,善待那些在你搬到其他东西时必须保持你的东西的可怜的灵魂......把它交给他们'valgrind clean':)

  • 0

    我完全不同意那些说OP是正确的或没有伤害的人 .

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

    但是,如果我在一个没有操作系统的环境中呢?什么地方没有?

    想象一下,现在您正在使用线程样式的中断并分配内存 . 在C标准ISO / IEC:9899中,内存的生命周期表示为:

    7.20.3内存管理功能1未指定连续调用calloc,malloc和realloc函数分配的存储的顺序和连续性 . 如果分配成功,则返回指针,以便可以将其指定给指向任何类型对象的指针,然后用于在分配的空间中访问此类对象或此类对象的数组(直到空间被明确释放) . 分配对象的生命周期从分配延伸到解除分配 . [...]

    因此,不能给出环境正在为您解放的工作 . 否则它将被添加到最后一句:“或直到程序终止 . ”

    换句话说:不释放记忆不仅仅是不好的做法 . 它产生非便携而不符合C的代码 . 至少可以将其视为“正确,如果以下情况:[......],则受环境支持” .

    但是在你根本没有操作系统的情况下,没有人为你做这项工作(我知道你通常不会在嵌入式系统上分配和重新分配内存,但有些情况你可能想要这样做 . )

    因此,一般来说,简单的C(OP被标记),这只是产生错误和不可移植的代码 .

  • 321

    当你退出时,留下记忆是完全没问题; malloc()从称为“堆”的内存区域分配内存,并在进程退出时释放进程的完整堆 .

    话虽这么说,人们仍然坚持认为在退出之前释放所有东西是好的一个原因是内存调试器(例如Linux上的valgrind)检测到不同的块作为内存泄漏,如果你还有“真正的”内存泄漏,它就会变成如果你最后得到“假”结果,更难发现它们 .

  • 3

    如果你分配了're using the memory you',那么你没有做错任何事 . 当您编写分配内存而不释放内存的函数(主程序除外)并且不将其提供给程序的其余部分时,它会成为一个问题 . 然后你的程序继续运行分配给它的内存,但无法使用它 . 您的程序和其他正在运行的程序被剥夺了内存 .

    编辑:它不是100%准确说其他正在运行的程序被剥夺了内存 . 操作系统总是允许他们使用它,代价是将程序交换到虚拟内存( </handwaving> ) . 但重点是,如果您的程序释放了不使用的内存,则不太可能需要虚拟内存交换 .

  • 11

    此代码通常可以正常工作,但请考虑代码重用的问题 .

    您可能已经编写了一些不释放已分配内存的代码片段,它以这样的方式运行,然后自动回收内存 . 似乎还好 .

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

    代码重用在企业中很常见 . 通常,公司拥有其员工 生产环境 的所有代码,每个部门都可以重复使用公司拥有的任何代码 . 因此,通过编写这种“天真无邪”的代码,您可能会对其他人造成潜在的麻烦 . 这可能会让你被解雇 .

  • -2

    这里真正的结果是什么?

    Your program leaked the memory. Depending on your OS, it may have been recovered.

    大多数现代 desktop 操作系统确实在进程终止时恢复泄漏的内存,这使得忽略该问题变得非常普遍,这可以从许多其他答案中看出 . )

    但是你依赖于一个你不应该依赖的安全功能,并且你的程序(或函数)可能会在这种行为导致"hard"内存泄漏的系统上运行, next 时间 .

    您可能正在内核模式下运行,或者在不使用内存保护作为权衡的老式/嵌入式操作系统上运行 . (MMU占用芯片空间,内存保护需要额外的CPU周期,并且要求程序员自己清理后也不要太多) .

    您可以按照自己喜欢的方式使用和重用内存,但请确保在退出之前取消分配所有资源 .

  • 22

    没有释放变量没有真正的危险,但是如果在不释放第一个块的情况下将指向内存块的指针分配给不同的内存块,则第一个块不再可访问但仍占用空间 . 这就是所谓的内存泄漏,如果你定期执行此操作,那么你的进程将开始消耗越来越多的内存,从其他进程中夺走系统资源 .

    如果进程是短暂的,你可以经常这样做,因为当进程完成时操作系统将回收所有分配的内存,但我建议养成释放你没有进一步使用的所有内存的习惯 .

  • 52

    实际上,OSTEP在线教科书中有一节介绍了操作系统的本科课程 .

    第6页的Memory API chapter中的相关部分为"Forgetting To Free Memory",其中给出了以下说明:

    在某些情况下,似乎没有调用free()是合理的 . 例如,你的程序是短暂的,很快就会退出;在这种情况下,当进程终止时,操作系统将清理其所有已分配的页面,因此本身不会发生内存泄漏 . 虽然这肯定“有效”(参见第7页的旁边),但这可能是一个坏习惯,所以要谨慎选择这样的策略

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

    在幕后,操作系统将用户看到的“虚拟地址”转换为指向物理内存的实际地址 .

    但是,共享物理内存等资源需要操作系统跟踪正在使用它的进程 . 因此,如果进程终止,那么操作系统的功能和设计目标就是回收进程的内存,以便它可以与其他进程重新分配和共享内存 .


    编辑:摘录中提到的旁边复制如下 .

    ASIDE:为什么在您的进程退出时没有泄漏内存当您编写一个短期程序时,您可以使用malloc()分配一些空间 . 该程序运行并即将完成:是否需要在退出之前多次调用free()?虽然似乎没有错,但任何真正意义上的记忆都不会“丢失” . 原因很简单:系统中实际存在两个级别的内存管理 . 第一级内存管理由OS执行,它在运行时将内存分发给进程,并在进程退出(或以其他方式死亡)时将其取回 . 第二级管理在每个进程中,例如在调用malloc()和free()时在堆内 . 即使您无法调用free()(从而泄漏堆中的内存),操作系统也会回收进程的所有内存(包括代码,堆栈以及相关堆栈的那些页面)当程序运行完毕 . 无论您的地址空间中的堆状态如何,操作系统都会在进程终止时收回所有这些页面,从而确保尽管您没有释放内存,但没有内存丢失 . 因此,对于短期程序,泄漏内存通常不会导致任何操作问题(尽管可能被认为是不良形式) . 当您编写长时间运行的服务器(例如永远不会退出的Web服务器或数据库管理系统)时,泄漏的内存是一个更大的问题,并且当应用程序内存不足时最终会导致崩溃 . 当然,泄漏内存在一个特定程序中是一个更大的问题:操作系统本身 . 再次向我们展示:编写内核代码的人有最艰巨的工作......来自操作系统的内存API章节:三个简单的部分Remzi H. Arpaci-Dusseau和Andrea C. Arpaci-Dusseau Arpaci-Dusseau书籍2015年3月(版本0.90)

  • 5

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

  • 12

    如果您正在从头开发应用程序,您可以做出一些有关何时免费通话的有根据的选择 . 你的示例程序很好:它分配内存,也许你让它工作几秒钟,然后关闭,释放它声称的所有资源 .

    如果您正在编写其他任何内容 - 服务器/长时间运行的应用程序或其他人使用的库,您应该期望在malloc的所有内容上免费调用 .

    忽略实用方面一秒钟,遵循更严格的方法更安全,并强迫自己释放你malloc的一切 . 如果您不习惯在编码时观察内存泄漏,则可能会轻易引发一些泄漏 . 换句话说,是的 - 没有它你就可以逃脱;但请小心 .

  • 3

    在这方面你是完全正确的 . 在小程序中,变量必须存在直到程序死亡,释放内存没有任何实际好处 .

    事实上,我曾经参与过一个项目,每个程序的执行都非常复杂但相对来说是短暂的,而且决定只是保持内存分配而不是通过错误解除分配来破坏项目的稳定性 .

    话虽这么说,在大多数程序中,这不是一个真正的选择,或者它可能导致你的内存耗尽 .

  • 2

    如果程序忘记在退出操作系统之前释放几兆字节将释放它们 . 但是如果你的程序一次运行数周并且程序中的循环忘记在每次迭代中释放几个字节,那么你将会遇到强大的内存泄漏,这会占用计算机中所有可用内存,除非你重新启动它如果程序用于一项非常重要的任务,即使它最初不是为一个人设计的,那么即使很小的内存泄漏也可能会很糟糕 .

相关问题