在不释放所有动态分配的资源的情况下结束程序是否有风险

Is ending a program without releasing all dynamically allocated resources risky?

本文关键字:程序 结束 是否 情况下 资源 动态分配 释放      更新时间:2023-10-16

我知道堆栈分配的资源以相反的顺序释放,因为它们是在作为 RAII 的一部分在函数结束时分配的。我一直在做一个项目,我用我正在使用的库中的"new"分配了大量内存,并且正在测试东西。我还没有添加关闭函数作为执行所有动态分配的初始化函数的对应项。当您关闭程序时,我很确定没有内存泄漏,因为操作系统应该回收分配的内存。至少任何现代操作系统,正如这个问题中所解释的类似于我的: 程序终止后动态分配内存 .

我想知道两件事:

1:在这种情况下,释放资源是否有特定的顺序?它是否与您编写的代码有关(即,您分配它的顺序),还是完全由操作系统来做它的事情?

2:我没有做一个关闭函数来反转初始化的原因是因为我对自己说我现在只是在测试东西,我稍后再做。做我正在做的事情是否有对任何东西造成任何损害的风险?我能想象到的更糟糕的是我链接的那个问题的答案中提到的内容,那就是操作系统无法回收内存,即使在程序退出后,您最终也会出现内存泄漏。

我遵循了 Bullet 物理库教程并初始化了一堆代码,如下所示:

pSolver = new btSequentialImpulseConstraintSolver;
pOverlappingPairCache = new btDbvtBroadphase();
pCollisionConfig = new btDefaultCollisionConfiguration();
pDispatcher = new btCollisionDispatcher(pCollisionConfig);
pDynamicsWorld = new btDiscreteDynamicsWorld(pDispatcher, pOverlappingPairCache, pSolver, pCollisionConfig);

并且目前永远不要对其中任何一个调用删除,因为正如我所说,我只是在测试。

这取决于资源。打开的文件将被关闭。内存将被释放。不会调用析构函数。创建的临时文件不会被删除。

程序退出后没有内存泄漏的风险。

由于程序可能会崩溃,因此有许多机制可以防止进程在停止后泄漏,并且泄漏通常并不那么糟糕。

事实上,如果你有很多分配在程序结束之前不删除,那么在你之后清理内核可能会更快。

但是,析构函数不会运行。这主要会导致临时文件不被删除。 此外,它还使调试实际内存泄漏更加困难。

我建议首先使用std::unique_ptr<T>而不是泄漏。

这取决于内存的实际分配方式和主机系统。

如果您只使用不覆盖operator new()的类,并且您使用的是保证在进程退出时释放内存资源的现代操作系统,则所有动态分配的内存都应在程序终止时释放。 不保证内存释放的顺序(例如,对象不会以与其构造相同的顺序或相反的顺序释放)。 在这种情况下,唯一真正的风险与主机操作系统中的错误有关,这些错误导致程序/进程的资源管理不当(对于现代Windows或Unix操作系统中的用户程序来说,这是一个低风险 - 但不是零风险)。

如果您使用任何覆盖operator new()的类(即在动态构造对象的过程中更改原始内存的分配方式),则风险取决于内存的实际分配方式 - 以及释放的要求。 例如,如果operator new()使用全局或系统范围的资源(例如互斥体、信号量、进程之间共享的内存),则存在程序无法正确释放这些资源的风险,然后间接导致使用相同资源的其他程序出现问题。 在实践中,根据此类的设计,所需的清理可能是析构函数、operator delete()或两者的某种组合 - 但是,无论如何完成,您的程序都需要显式释放此类对象(例如,对应于new表达式的delete表达式)以确保正确释放全局资源。

一个风险是不会调用动态分配对象的析构函数。 如果程序依赖于析构函数执行除释放动态分配的内存(可能由类构造函数分配并由其他成员函数管理)之外的任何操作,则不会执行其他清理操作。

如果您的程序将在没有现代操作系统的主机系统上构建和运行,则无法保证将回收动态分配的内存。

如果程序中的代码将在较大的长时间运行的程序中重用(例如,重命名main()函数,然后在循环中从另一个程序调用),则代码可能会导致该较大的程序出现内存泄漏。

这很好,因为操作系统(除非是一些奇特或古老的操作系统)在该过程结束后不会泄漏内存。套接字和文件句柄也是如此;它们将在进程退出时关闭。不清理自己是不好的风格,但如果你不清理,对整个操作系统环境不会造成伤害。

但是,在您的示例中,在我看来,您实际需要释放自己的唯一内存是pDynamicsWorld,因为其他内存应该由btDiscreteDynamicsWorld实例清理。您将它们作为构造函数参数传递,我怀疑当它们被销毁时pDynamicsWorld它们会自动销毁。您应该阅读文档以确保。

但是,再使用delete就不是很好(因为它不安全)。因此,您可以使用std::make_unique函数模板安全地创建unique_ptr,而不是使用delete销毁pDynamicsWorld

#include <memory>
// ...
// Allocate everything else with 'new' here, as usual.
// ...
// Except for this one, which doesn't seem to be passed to another
// constructor.      
auto pDynamicsWorld = std::make_unique<btDiscreteDynamicsWorld>(
pDispatcher, pOverlappingPairCache, pSolver, pCollisionConfig);

现在,pDispatcherpOverlappingPairCachepSolverpCollisionConfig应该被pDynamicsWorld自动销毁,而pDynamicsWorld本身在超出范围时会自动销毁,因为它是unique_ptr

但是,再说一遍:阅读Bullet Physics 的文档,以检查作为参数传递给 Bullet Physics 类构造函数的对象是否真的被自动清理。