当您在malloc之后不释放时,真正发生了什么?

多年来,这一直困扰着我。

我们都在学校里受教(至少在我以前是这样),您必须释放分配的每个指针。 但是,对于不释放内存的实际成本,我有点好奇。 在某些明显的情况下,例如在循环或线程执行的一部分内调用malloc时,释放非常重要,这样就不会发生内存泄漏。 但是,请考虑以下两个示例:

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

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

真正的结果是什么? 我的想法是该过程终止,然后无论如何堆空间都消失了,因此错过free调用没有任何危害(但是,我确实意识到无论如何对于关闭,可维护性和良好实践而言,它的重要性)。 我的想法对吗?

其次,假设我有一个程序有点像shell。 用户可以声明aaa = 123类的变量,并将其存储在某些动态数据结构中以备后用。 显然,显然您会使用一些可调用* alloc函数的解决方案(哈希映射,链接列表等)。 对于这种程序,调用malloc之后永远释放是没有意义的,因为这些变量必须在程序执行期间始终存在,并且没有很好的方法(我可以看到)用静态分配的空间来实现。 分配一堆但仅在过程结束时释放的内存是不好的设计吗? 如果是这样,还有什么选择?


#1楼

您是正确的,没有造成任何伤害,退出时更快

造成这种情况的原因有很多:

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

  • 无论如何,几乎所有的free()实现都不会将内存返回给操作系统。

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

关于未来的代码重用possiblility无谓justifing OPS的肯定 :这是一个考虑因素,但它无疑不是敏捷的方式。 亚尼!


#2楼

我完全不同意说OP是正确的或没有危害的每个人。

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

但是,如果我只是在没有操作系统的环境中怎么办? 那里什么都没有?

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

7.20.3内存管理功能

1连续调用calloc,malloc和realloc函数分配的存储的顺序和连续性未指定。 如果分配成功,则返回的指针将进行适当对齐,以便可以将其分配给指向任何类型对象的指针,然后将其用于访问分配的空间中的此类对象或此类对象的数组(直到明确释放该空间为止) 。 分配对象的生命周期从分配一直到释放。[...]

因此,不必认为环境正在为您完成释放工作。 否则,它将被添加到最后一句话:“或者直到程序终止。”

因此,换句话说:不释放内存不仅仅是不好的做法。 它产生不可移植且不符合C规范的代码。 至少可以将其视为“正确的,如果以下情况:[...]受环境支持”。

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

因此,以普通的普通C语言(OP被标记)来说,这只是在产生错误且不可移植的代码。


#3楼

真正的结果是什么?

您的程序泄漏了内存。 根据您的操作系统,它可能已被恢复。

大多数现代台式机操作系统的确会在进程终止时恢复泄漏的内存,令人遗憾的是,忽略该问题很普遍,如此处的许多其他答案所示。)

但是,您依赖的是不应使用的安全功能,并且您的程序(或功能)可能会在系统上运行,而该行为的确会导致下次 “硬”内存泄漏。

您可能正在内核模式下运行,或者在未采用内存保护作为代价的老式/嵌入式操作系统上运行。 (MMU占用了模腔,内存保护花费了额外的CPU周期,并且要求程序员自己清理也就不多了)。

您可以按自己喜欢的任何方式使用和重用内存,但是在退出之前,请确保已释放所有资源。


#4楼

OSTEP在线教科书中实际上有一个部分针对操作系统的本科课程,它确切地讨论了您的问题。

相关部分是第6页上的Memory API章节中的“忘记释放内存”,其中给出了以下说明:

在某些情况下,似乎不调用free()是合理的。 例如,您的程序寿命很短,将很快退出; 在这种情况下,当进程终止时,操作系统将清理所有分配的页面,因此,不会发生内存泄漏。 尽管这肯定是“有效的”(请参阅​​第7页的旁白),但养成这种习惯可能是个坏习惯,因此请谨慎选择这种策略

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

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

但是,共享资源(例如物理内存)要求操作系统跟踪正在使用哪些进程。 因此,如果进程终止,则回收操作系统的内存在操作系统的能力和设计目标之内,以便它可以重新分配内存并与其他进程共享。


编辑:摘录中提到的一旁复制如下。

旁白为什么程序退出后不会留下记忆

在编写短期程序时,可以使用malloc()分配一些空间。 该程序将运行并即将完成:是否需要在退出前多次调用free() ? 尽管这样做似乎是错误的,但实际上任何内存都不会“丢失”。 原因很简单:系统中实际上有两个级别的内存管理。 操作系统执行内存管理的第一级,操作系统在运行时将内存分配给进程,并在进程退出(或以其他方式终止)时将其收回。 第二层管理在每个进程内,例如,在调用malloc()free()时在堆内。 即使您无法调用free() (从而导致堆中的内存泄漏),操作系统也会在程序执行时回收进程的所有内存(包括用于代码,堆栈和相关堆的页面)完成运行。 无论您的地址空间中的堆状态如何,当进程终止时,操作系统都会收回所有这些页面,从而确保即使您没有释放它也不会丢失任何内存。

因此,对于寿命短的程序,内存泄漏通常不会引起任何操作问题(尽管它可能被认为是较差的形式)。 当您编写长时间运行的服务器(例如Web服务器或数据库管理系统,这些服务器永不退出)时,内存泄漏是一个更大的问题,当应用程序内存不足时,最终将导致崩溃。 当然,内存泄漏是一个特定程序(操作系统本身)内更大的问题。 再次向我们展示:编写内核代码的人工作最艰苦……

从第7页的“ 内存API”一章

操作系统:三个简单的部分
Remzi H.Arpaci-Dusseau和Andrea C.Arpaci-Dusseau Arpaci-Dusseau Books 2015年3月(0.90版)


#5楼

如果程序在退出之前忘记释放几兆字节,则操作系统将释放它们。 但是,如果您的程序一次运行数周,并且程序内部的循环忘记每次迭代都释放几个字节,则可能会发生巨大的内存泄漏,除非您定期重新启动它,否则它将吞噬计算机中的所有可用内存。基础=>即使程序最初用于某个大型任务,即使很小的内存泄漏也可能是很糟糕的。


#6楼

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

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

分配误区4:非垃圾收集程序应始终释放其分配的所有内存。

真相:频繁执行的代码中省略的重分配会导致泄漏增加。 它们很少被接受。 但是在程序退出之前保留分配的内存最多的程序通常会在没有任何介入释放的情况下执行得更好。 如果没有免费的内存,则Malloc易于实现。

在大多数情况下, 在程序退出之前释放内存是没有意义的。 操作系统仍会收回它。 自由会触碰并翻页死物; 操作系统不会。

结果:请小心“泄漏检测器”,它会统计分配情况。 一些“泄漏”是好的!

也就是说,您应该真正避免所有内存泄漏!

第二个问题:您的设计还可以。 如果您需要存储某些内容,直到应用程序退出,则可以通过动态内存分配来实现。 如果您不预先知道所需的大小,则不能使用静态分配的内存。


#7楼

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

因此,通常来说,这没有什么坏处,除了拥有超出所需存储空间的运行时成本。 当然,在您给出的示例中,您想要保留一个变量的内存,直到清除该变量为止。

但是,这是一种很好的样式,可以在不再需要内存时立即释放内存,并在程序退出时释放内存。 这更多的是了解您正在使用的内存,并思考是否仍需要它。 如果不跟踪,则可能会发生内存泄漏。

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


#8楼

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

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


#9楼

退出时释放内存是完全可以的。 malloc()从称为“堆”的内存区域分配内存,并在进程退出时释放进程的完整堆。

话虽这么说,人们仍然坚持认为最好在退出之前释放所有内容的一个原因是内存调试器(例如Linux上的valgrind)将未释放的块检测为内存泄漏,并且如果您还存在“实际的”内存泄漏,它将变成如果最后还得到“伪造”结果,则更难发现它们。


#10楼

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

编辑:说其他正在运行的程序被剥夺了内存并不是100%准确。 操作系统始终可以让他们使用它,而以将程序换出到虚拟内存( </handwaving> )为</handwaving> 。 但是,要点是,如果程序释放了未使用的内存,则不太可能需要进行虚拟内存交换。


#11楼

没错,进程退出时内存会自动释放。 有些人力争在进程终止时不进行大量清理,因为它将全部放弃给操作系统。 但是,在程序运行时,应释放未使用的内存。 否则,如果工作集太大,最终可能会耗尽或导致过多的页面调度。


#12楼

如果您是从头开始开发应用程序,则可以对何时免费拨打电话做出一些明智的选择。 您的示例程序很好:它分配了内存,也许您让它工作了几秒钟,然后关闭了,释放了它声称的所有资源。

但是,如果要编写其他任何东西(服务器/长时间运行的应用程序或供其他人使用的库),则应该期望对malloc的所有内容进行免费调用。

忽略实用主义的一秒钟,遵循更严格的方法会更安全,并强迫自己释放malloc的所有内容。 如果您不习惯在每次编写代码时都注意内存泄漏,则可以轻松地引发一些泄漏。 因此,换句话说,是的-没有它,您可以逃脱。 不过请小心。


#13楼

您在这方面绝对正确。 在琐碎的小型程序中,必须有一个变量,直到程序终止运行,释放内存并没有真正的好处。

实际上,我曾经参与过一个项目,该项目的每次执行都非常复杂,但寿命相对较短,因此决定只保留内存,而不要通过错误地分配内存来破坏项目的稳定性。

话虽这么说,在大多数程序中这并不是一个真正的选择,否则它可能会导致您内存不足。


#14楼

我认为您的两个示例实际上只是一个示例: free()应该仅在过程结束时出现,正如您指出的那样,因为过程正在终止,所以这是没有用的。

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


#15楼

=== 将来的校对代码重用如何? ===

如果您编写代码来释放对象,那么您将代码限制为仅在可以依靠关闭的进程来释放内存的情况下才可以安全使用,即少量使用一次项目或“扔掉” [1]项目)...,您知道流程何时结束。

如果您确实编写了释放所有动态分配的内存的代码,那么将来您将对代码进行验证,并让其他人在更大的项目中使用它。


[1]关于“一次性”项目。“丢弃”项目中使用的代码具有不被丢弃的方式。您知道已经过去了十年的下一件事,并且您的“丢弃”代码仍在使用中)。

我听到一个故事,说某人只是为了好玩而编写一些代码,以使其硬件更好地工作。他说:“ 只是一种爱好,不会变得又大又专业 ”。几年后,许多人都在使用他的“爱好”代码。


#16楼

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

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

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

代码重用在企业中很典型。 通常,公司拥有员工生产的所有代码,每个部门都可以重复使用公司拥有的任何代码。 因此,通过编写这种“看起来很天真”的代码,您可能会引起其他人的头痛。 这可能会让您被解雇。


#17楼

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

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

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

  1. 用户键入“取消设置”
  2. 输入了我的清理功能,用于处理信号或用户键入“退出”

我想不出有任何现代 OS在进程退出后不会回收内存。 再说一次,free()很便宜,为什么不清理呢? 正如其他人所说,valgrind之类的工具非常适合发现您确实需要担心的泄漏。 即使您示例中的块被标记为“仍可到达”,但在尝试确保没有泄漏时,它只是输出中的额外噪声。

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

char *t;

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

如果那是在派生/守护进程之前发生的(理论上是永远运行),则您的程序泄漏的不确定大小为t 255次。

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

的确,对可怜的灵魂要好些,当您继续从事其他工作时,这些灵魂必须维护您的东西..交给他们“ valgrind clean” :)

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值