如何在禁用C++异常(VS2010)的情况下确保内存耗尽的鲁棒性?

10
我正在开发一款性能关键的动态链接库(DLL),并且需要保持二进制文件尽可能小。由于它不会显式地抛出任何异常,所以我想完全禁用异常支持。然而,有一个例外(不是故意的双关语):当内存耗尽(OOM)时,我必须向应用程序报告错误代码,以便它有机会优雅地处理问题。代码库太大了,无法检查每个分配并传播错误,并包含我不应该触及的外部代码。因此,我想在我的DLL导出函数中捕获OOM异常。
快速测试表明,在禁用Visual C++ 2010中的C++异常时(即没有/EHa、/EHsc或/EHs标志),当分配太多内存时,它仍会跳转到一个catch(std::bad_alloc&)块中。
所以它似乎按预期工作。然而,我得到以下级别1警告: "C4530:使用C++异常处理程序,但未启用展开语义。请指定/EHsc"。MSDN说 "在抛出异常的函数和捕获异常的函数之间的帧中具有自动存储的对象将不被销毁"。
这里会失去什么?只要通过库创建的任何东西可以被删除,应用程序就可以重新开始(如果它选择这样做的话),保持未定义状态是可以接受的。是否存在无法恢复的内存泄漏的风险?
DLL是否使用单独的内存池?如果是这样,我是否可以在不需要应用程序卸载DLL的情况下清除它?我可以轻松地让我的库忽略任何进一步的(导出的)函数调用,直到应用程序执行重新初始化。
感谢您的建议。

DLL使用独立的内存池吗? - thang
如果可以的话,我能在不需要应用程序卸载DLL的情况下清除它吗?是的,只需从new中删除内容并从malloc中释放内容即可。 - thang
1
没有异常处理意味着在堆栈上创建的对象(以及构造函数中失败的对象)不会被销毁。如果 bad_alloc 发生时你只是要退出程序,那么我想你是可以接受的 [只要你没有奇怪的资源没有在程序退出时清理干净 - 但大多数情况下是可以的]。如果你想在 bad_alloc 后“继续”,则代码将需要跟踪对象并销毁在 throwcatch 之间在堆栈框架中创建的所有对象。你可以写一些在析构函数中打印输出的小代码来进行尝试。 - Mats Petersson
@thang 我的意思是自动删除它。我使用多个第三方组件,其中任何一个都可能抛出OOM异常,因此能够释放从我的DLL中分配的任何内存(并在此过程中终止任何线程/关闭任何文件句柄)将是很好的。静态链接C运行时会增加二进制文件大小,这也是不可取的。 - Nicolas Capens
1个回答

1

一些准备工作:

我不知道在标准中启用异常处理之前抛出异常是否属于未定义行为,但您肯定不会从堆栈上的对象获得堆栈展开/析构函数调用。

如果您正在使用 RAII 为互斥锁、文件、内存等编写 C++ 风格的代码,则这是非常糟糕的事情。

然后继续,假设您的代码基本上是 C 风格的代码:

1)如果您静态链接到 C 运行时库,则您的 DLL 将不与主应用程序共享堆。卸载 DLL 应释放泄漏的内存--但再次要注意其他资源。

2)如果您动态链接到 C 运行时(非常常见),则您正在共享一个堆。您将必须有一种方法手动释放从 DLL 分配的任何内存。

由于我自己已经过多地涉足 DLL 边界问题,因此我建议进行快速基准测试,以查看启用异常时需要支付的费用。根据您的平台和编译器,未抛出的异常可能会对性能产生相当微不足道的影响。


这不是 C 风格的代码,其中一部分也不在我的控制范围之内。虽然我在正常操作下相信这个第三方代码,但当出现 OOM 异常时,我认为无论是否启用异常处理,我都不能做出任何假设。"您将需要一种手动释放从 DLL 分配的内存的方法。" 这正是我的问题所在。 :-) - Nicolas Capens
抱歉,但我认为你想要的是不可能实现的。为了在我的插件类型系统中实现“从DLL手动释放”,我需要所有客户端代码使用由我的核心提供给DLL的自定义分配例程进行分配。这种关联允许核心像你想要的那样卸载DLL。但如果你自己无法强制执行这一点,那么我认为这是不可能的。 - Stephen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接