【问题标题】:When you exit a C application, is the malloc-ed memory automatically freed?当您退出 C 应用程序时,malloc-ed 内存是否会自动释放?
【发布时间】:2010-02-06 15:37:21
【问题描述】:

假设我有以下 C 代码:

int main () {
  int *p = malloc(10 * sizeof *p);
  *p = 42;
  return 0;  //Exiting without freeing the allocated memory
}

当我编译和执行那个C程序时,即在内存中分配了一些空间之后,我分配的内存在我退出应用程序并且进程终止后是否仍然被分配(即基本上占用空间)?

【问题讨论】:

  • 清理内存是“好风格”,不是因为您可能在没有受保护内存的操作系统上运行(这是下面的主要建议),而是因为它增加了您的可能性'会发现内存泄漏,并保持你的代码精简和正确......
  • 我知道这并不重要,因为它只是一个示例,但是如果您只存储一个,为什么要为 10 个ints 分配内存呢?

标签: c memory-management


【解决方案1】:

这取决于操作系统。大多数现代(以及所有主要)操作系统都会在程序结束时释放程序未释放的内存。

依赖这个是不好的做法,最好明确地释放它。问题不仅在于您的代码看起来很糟糕。您可能决定要将您的小程序集成到一个更大、长期运行的程序中。过了一会儿,您必须花费数小时追踪内存泄漏。
依赖操作系统的特性也会降低代码的可移植性。

【讨论】:

  • 我曾经在嵌入式平台上遇到过win98,基于那次经验,我可以说它在程序关闭时不会释放内存。
  • @Ken 这是一个例子。此外,在 YAGNI 和草率编码之间也有一条线。不释放资源会越过它。 YAGNI 原则也适用于功能,而不是使程序正常工作的代码。 (并且不释放内存是一个错误)。
  • +1:要考虑的最重要的事情是内存管理正如 Yacoby 非常正确地陈述的那样:“操作系统的一个特性”。除非我弄错了,否则编程语言不会定义程序执行之前或之后会发生什么。
  • 手动释放内存需要更多时间,需要更多代码,并引入错误的可能性(告诉我你从未见过释放代码中的错误!)。对于您的特定用例,故意省略在各方面都更糟糕的东西并不是“草率”。除非或直到您打算在进程终止后无法释放页面的某些古老/小型系统上运行它,或者将其集成到更大的程序(YAGNI)中,否则它对我来说似乎是一个净损失。我知道一想到不自己清理它会伤害程序员的自尊心,但实际上它实际上更好吗?
  • 任何在 SO 上提出内存泄漏的人都应该被剥夺所有声誉和徽章
【解决方案2】:

一般而言,现代通用操作系统会在进程终止后进行清理。这是必要的,因为替代方案是让系统随着时间的推移失去资源,并且由于程序编写不佳或只是具有很少发生的泄漏资源的错误而需要重新启动。

让您的程序显式释放其资源可能是一种很好的做法,原因有多种,例如:

  • 如果您有其他资源没有在退出时由操作系统清理,例如临时文件或任何类型的更改外部资源的状态,那么您将需要代码在退出时处理所有这些事情,这通常与释放内存完美结合。
  • 如果您的程序开始有更长的生命周期,那么您将不希望退出only 释放内存的方式。例如,您可能希望将您的程序转换为一个服务器(守护程序),该服务器(守护程序)在处理单个工作单元的许多请求时保持运行,或者您的程序可能成为更大程序的一小部分。

但是,这里有一个跳过释放内存的原因:高效关机。例如,假设您的应用程序在内存中包含一个大缓存。如果当它退出时,它会遍历整个缓存结构并一次释放一个,这没有任何用处,而且浪费资源。特别是,考虑包含缓存的内存页面已被操作系统交换到磁盘的情况;通过遍历结构并释放它,您将所有这些页面一次全部带回内存,浪费大量时间和精力而没有实际好处,甚至可能导致系统上的其他程序获取换了!

作为一个相关的例子,有一些高性能服务器通过为每个请求创建一个进程,然后在完成后退出;通过这种方式,它们甚至不必跟踪内存分配,并且根本不会进行任何释放或垃圾收集,因为在进程结束时所有内容都会消失在操作系统的空闲内存中。 (同样的事情可以在使用自定义内存分配器的进程中完成,但需要非常仔细的编程;本质上是在 OS 进程中建立自己的“轻量级进程”概念。)

【讨论】:

    【解决方案3】:

    我很抱歉在最后一个帖子发布到这个帖子之后这么久才发帖。

    还有一点。并非所有程序都能优雅地退出。崩溃和 ctrl-C 等将导致程序以不受控制的方式退出。如果您的操作系统没有释放您的堆、清理您的堆栈、删除静态变量等,您最终会因内存泄漏或更糟的情况导致系统崩溃。

    除此之外,有趣的是,Ubuntu 中的崩溃/中断,我怀疑所有其他现代操作系统确实存在“处理”资源的问题。当程序结束/崩溃时,套接字、文件、设备等可以保持“打开” . 在优雅退出之前,在清理过程中使用“句柄”或“描述符”关闭任何内容也是一种很好的做法。

    我目前正在开发一个大量使用套接字的程序。当我陷入困境时,我必须 ctrl-c 退出它,因此,我的套接字被搁置了。我添加了一个 std::vector 来收集所有打开的套接字的列表和一个捕获 sigint 和 sigterm 的 sigaction 处理程序。处理程序遍历列表并关闭套接字。我计划在 throw 之前使用类似的清理程序,这会导致提前终止。

    有人愿意评论这个设计吗?

    【讨论】:

    • 我很高兴你这么说,因为我有一个程序会留下套接字资源,而我们的 Ubuntu 系统需要每两周重新启动一次,否则内存开始耗尽,并且内存充足。如果您忘记清理它们,我不确定系统资源是否会被拆除。
    • Stackoverflow 不是论坛;回答一个老问题没什么错。 meta.stackexchange.com/questions/20524/reviving-old-questions
    【解决方案4】:

    这里发生的事情(在现代操作系统中)是您的程序在其自己的“进程”中运行。这是一个操作系统实体,拥有自己的地址空间、文件描述符等。您的malloc 调用正在从“堆”分配内存,或者分配给您的进程的未分配内存页面。

    当您的程序结束时,如本例所示,分配给您的进程的所有资源都会被操作系统简单地回收/拆除。在内存的情况下,分配给您的所有内存页面都被简单地标记为“空闲”并回收以供其他进程使用。页面是一个比 malloc 处理的更低级别的概念——因此,随着整个事情的清理,malloc/free 的细节都被简单地洗掉了。

    这相当于,当您用完笔记本电脑并想把它送给朋友时,您不必费心单独删除每个文件。您只需格式化硬盘即可。

    正如所有其他回答者所指出的那样,所有这一切都表明,依赖这个不是好习惯:

    1. 您应该始终通过编程来处理资源,在 C 语言中这也意味着内存。您最终可能会将代码嵌入到库中,或者最终运行的时间可能比您预期的要长。
    2. 某些操作系统(较旧的操作系统,可能还有一些现代嵌入式操作系统)可能无法保持如此硬的进程边界,并且您的分配可能会影响其他人的地址空间。

    【讨论】:

      【解决方案5】:

      是的。操作系统清理资源。嗯...旧版本的 NetWare 没有。

      编辑:正如 San Jacinto 所指出的,肯定有一些系统(除了 NetWare)不这样做。即使在一次性程序中,我也会尝试养成释放所有资源的习惯,只是为了保持这种习惯。

      【讨论】:

      • 我没有反对,但这对后代来说是一个非常危险的帖子。 DOS 仍在许多嵌入式平台上使用,我严重怀疑它是否会为您清理内存。笼统的概括是错误的。
      • @San Jacinto:这是一个很好的观点。这就是为什么我确实参考了 NetWare,但它可能需要澄清一下。我会稍微修改一下。
      • @San DOS 不是多任务操作系统 - 当一个 DOS 程序(不包括 TSR)结束时,所有内存都可用于下一个要加载的程序。
      • @Neil 感谢您的提醒,但我指的是一个类似 TSR 的程序,它会在事件发生时启动,这是嵌入式系统的常见用途。尽管如此,感谢您的专业知识和澄清我失败的地方:)
      【解决方案6】:

      是的,操作系统会在进程结束时释放所有内存。

      【讨论】:

      • 我不明白为什么这被否决了。当进程终止时,malloc 的内存将被释放(malloc 的维基百科定义如此)
      • 维基百科并不是所有操作系统的手册。 大多数 现代操作系统会回收内存,但并非所有(尤其不是所有旧操作系统)都会这样做。除此之外,malloc 只能承诺 C 将如何处理内存;按照设计,C 不保证 C 本身之外的任何行为。如果应用程序意外终止,运行时库做出的任何承诺都是无效的,因为它不再能够兑现承诺。
      【解决方案7】:

      视情况而定,操作系统通常会为您清理它,但如果您正在开发例如嵌入式软件,那么它可能不会发布。

      只要确保你释放它,它可以为你节省很多时间,当你可能想将它集成到一个大型项目中时。

      【讨论】:

        【解决方案8】:

        这确实取决于操作系统,但是对于您将遇到的所有操作系统,内存分配将在进程退出时消失。

        【讨论】:

          【解决方案9】:

          我认为直接释放是最好的。未定义的行为是最糟糕的事情,因此,如果您在流程中仍然定义了它的情况下拥有访问权限,那就去做吧,人们已经给出了很多很好的理由。

          至于我在 W98 中在哪里或是否发现,真正的问题是“何时”(我没有看到强调这一点的帖子)。一个小的模板程序(用于 MIDI SysEx 输入,使用各种 malloc 空间)将释放 WndProc 的 WM_DESTROY 位中的内存,但是当我将它移植到一个更大的程序时,它在退出时崩溃了。我认为这意味着我试图释放操作系统在更大的清理过程中已经释放的东西。如果我在 WM_CLOSE 上执行此操作,然后调用 DestroyWindow(),一切正常,立即干净退出。

          虽然这与 MIDI 缓冲区并不完全相同,但它们的相似之处在于最好保持流程完整,彻底清理,然后退出。使用适度的内存块,这是非常快的。我发现许多小缓冲区在操作和清理方面的工作速度要快于较少的大缓冲区。

          正如有人在避免将大内存块从磁盘上的交换文件中拉回时所说的那样,可能存在例外情况,但即使这样也可以通过保留更多和更小的分配空间来最小化。

          【讨论】:

            猜你喜欢
            • 2011-01-13
            • 2019-03-09
            • 1970-01-01
            • 2012-01-07
            • 1970-01-01
            • 2023-03-30
            • 2010-11-21
            • 2023-02-13
            相关资源
            最近更新 更多