假设我有以下C代码:
int main () {
int *p = malloc(10 * sizeof *p);
*p = 42;
return 0; //Exiting without freeing the allocated memory
}
当我编译并执行该C程序时,即在内存中分配了一些空间后,我退出应用程序且过程终止后,分配的内存是否仍会分配(即基本占用空间)?
假设我有以下C代码:
int main () {
int *p = malloc(10 * sizeof *p);
*p = 42;
return 0; //Exiting without freeing the allocated memory
}
当我编译并执行该C程序时,即在内存中分配了一些空间后,我退出应用程序且过程终止后,分配的内存是否仍会分配(即基本占用空间)?
Answers:
这取决于操作系统。当程序结束时,大多数现代(以及所有主要)操作系统将释放程序未释放的内存。
依靠这种做法是不好的做法,最好将其明确释放。问题不仅仅在于您的代码看起来不好。您可能会决定将小程序集成到一个较大的,长期运行的程序中。然后过了一会儿,您必须花费数小时来跟踪内存泄漏。
依靠操作系统的功能还使代码的可移植性降低。
通常,现代通用操作系统会在终止进程后进行清理。这是必需的,因为替代方法是使系统随着时间的流逝而丢失资源,并且由于程序编写不当或仅具有很少发生的会泄漏资源的错误而导致程序需要重新启动。
出于各种原因,无论如何都要让程序明确释放其资源可能是一个好习惯,例如:
但是,以下是跳过释放内存的原因:有效关机。例如,假设您的应用程序在内存中包含大缓存。如果退出时,它将遍历整个缓存结构并一次将其释放一次,这将无济于事,并浪费资源。尤其要考虑以下情况:包含高速缓存的内存页已被操作系统交换到磁盘上;通过遍历结构并释放它,您将所有这些页面一次全部带回到内存中,浪费大量时间和精力,没有任何实际好处,甚至可能导致系统上的其他程序被换出!
举一个相关的例子,有一些高性能服务器,它们通过为每个请求创建一个进程,然后在完成后退出该进程来工作。通过这种方式,他们甚至不必跟踪内存分配,也根本不需要进行任何释放或垃圾回收,因为在此过程结束时,一切都将消失在操作系统的空闲内存中。(可以使用自定义内存分配器在进程内完成相同的操作,但需要非常仔细的编程;本质上是在OS进程内制定自己的“轻量级进程”概念。)
我很抱歉在此主题的最后发布之后发布了这么长时间。
还有一点。并非所有程序都可以正常退出。崩溃和ctrl-C等将导致程序以不受控制的方式退出。如果您的操作系统没有释放堆,清理堆栈,删除静态变量等,最终将导致系统因内存泄漏或更糟而崩溃。
除了有趣的是,Ubuntu中的崩溃/中断,而且我怀疑所有其他现代OS的确都存在“已处理”资源的问题。套接字,文件,设备等在程序结束/崩溃时可能保持“打开”状态。在正常退出之前,作为清理工作的一部分,使用“句柄”或“描述符”关闭所有内容也是一种很好的做法。
我目前正在开发一个大量使用套接字的程序。当我陷入死机时,我必须从其中退出ctrl-c,从而搁浅我的套接字。我添加了一个std :: vector来收集所有打开的套接字的列表以及捕获sigint和sigterm的sigaction处理程序。处理程序遍历列表并关闭套接字。我计划在抛出异常之前使用类似的清理例程,以免提前终止。
有人愿意对此设计发表评论吗?
在现代操作系统中,这里发生的是您的程序在其自己的“进程”中运行。这是一个操作系统实体,具有其自己的地址空间,文件描述符等。您的malloc
调用是从分配给进程的“堆”或未分配的内存页分配内存。
如本例所示,当程序结束时,分配给进程的所有资源都将由操作系统简单地回收/销毁。对于内存,分配给您的所有内存页面都被简单地标记为“空闲”并被回收以供其他进程使用。页面是比malloc处理的概念更底层的概念-结果,随着整个事情的清理,所有malloc / free的细节都被简单地洗掉了。
这在道德上是等效的,当您用完笔记本电脑并想把它送给朋友时,您不必费心单独删除每个文件。您只需格式化硬盘。
正如所有其他答复者所指出的,所有这些都说明,依靠这种做法不是好习惯:
是。操作系统清理资源。好吧……旧版本的NetWare没有。
编辑:正如San Jacinto所指出的,肯定有一些系统(除了NetWare以外)没有这样做。即使在一次性程序中,我也会养成释放所有资源的习惯,只是为了保持这种习惯。
是的,进程结束时,操作系统将释放所有内存。
malloc
只能保证C将对内存进行处理。根据设计,C不能保证在C自身以外的行为方面没有任何事情。如果应用程序意外死机,则运行时库发出的所有承诺都是无效的,因为不再履行它们。
这确实取决于操作系统,但是对于您将遇到的所有操作系统,当进程退出时,内存分配将消失。
我认为直接释放是最好的。未定义的行为是最糟糕的事情,因此,如果您仍可以在过程中定义它的同时进行访问,那么请这样做,这是有很多充分的理由说明了人们给出的理由。
至于在哪里,或者是否,我发现在W98中,真正的问题是“何时”(我没有看到强调这一点的文章)。一个小型模板程序(用于MIDI SysEx输入,使用各种malloc分配的空间)将释放WndProc的WM_DESTROY位中的内存,但是当我将此移植到较大的程序时,它在退出时崩溃。我以为这意味着我要释放操作系统在较大清理期间已经释放的内容。如果我是在WM_CLOSE上执行的,则称为DestroyWindow(),则一切正常,可以立即退出。
尽管这与MIDI缓冲区不完全相同,但有一个相似之处,就是最好保持过程完整无缺,完全清理然后退出。使用适度的内存块,这非常快。我发现许多小型缓冲区的操作和清理速度要比大型缓冲区少。
正如有人说的那样,可以避免从磁盘上的交换文件中拖出大内存块,但是可能存在例外,但是即使这样,也可以通过保留更多和更小的分配空间来最小化。