跨DLL边界抛出C++异常

35

我已经阅读了很多关于不应该在一个DLL中分配堆内存并从该DLL之外进行释放的事情。但是抛出一个仅仅是临时(大多数异常对象都是如此)的异常对象呢?例如:

throw my_exception( args ); // temporary: no heap allocation

当异常对象在 DLL 外部被捕获时,该对象的析构函数最终会被执行,非堆内存会被回收。由于它不是堆内存,所以这样做是否可以?


@Mark:当异常被引用捕获时,这仍然是真的吗?你有来源吗? - Ben Voigt
1
@Ben:根据N3225,15.1 抛出异常3:throw表达式初始化一个临时对象...5:异常对象的内存以一种未指定的方式分配... @Paul:5:当被抛出的对象是类对象时,即使复制/移动操作被省略,复制/移动构造函数和析构函数也必须可访问。 - Eugen Constantin Dinca
1
@Ben:从N3225中,15.3处理异常 16:在异常声明中声明的对象或者如果异常声明没有指定名称,则从异常对象复制初始化一个临时对象(12.2)... 17:...当处理程序声明对非常量对象的引用时,对所引用对象的任何更改都是对在执行throw表达式时初始化的临时对象的更改,并且如果重新抛出该对象,则会产生影响。 - Eugen Constantin Dinca
@Paul,理论上使用相同的副本可能是可行的,但实际上,由于堆栈被解开,对象将停止存在。 - Mark Ransom
@Eugen:谢谢,这正是我想知道的。 - Ben Voigt
显示剩余2条评论
2个回答

33

在跨DLL边界抛出C++异常只有当所有模块使用相同的C++运行时库时才可能,这样它们也共享一个堆。但是这可能会增加维护负担,特别是涉及多个供应商的库时,因此不建议使用。

如果您想要跨多个编译器/编译器版本/编译器设置具有可移植性的错误处理,可以使用返回代码或操作系统提供的异常(例如Windows上的SEH)。


你写道,“...在这种情况下,它们也共享一个堆。” 我特别询问的是没有涉及堆内存的情况。 - Paul J. Lucas
@Paul:我的意思是,如果你有一个理由(而且有很多好的理由)不想在DLL之间共享堆内存,那么同样的理由也会阻止你在DLL之间共享异常。 - Ben Voigt
那么在DLL之间共享一个简单的POD结构也不行吗?如果DLL中的函数通过值返回一个结构体,那么这个临时变量最终会被销毁。同样的问题吗?如果不是,为什么不是? - Paul J. Lucas
13
@Paul:问题不在于异常结构体本身,而在于编译器内部用于查找匹配的catch块和执行堆栈展开的数据,所有这些都高度依赖于编译器,并可能使用全局变量,甚至可能使用堆。如果所有模块没有共享运行时,则会出现问题。 - Ben Voigt

1

这取决于内存是如何分配的,以及执行此操作的机制(“运行时”或“内存管理器”)是否在特定DLL和应用程序的其他部分之间共享。例如,根据详细信息,也可以使用throw new my_exception(args);

您可以使您的异常引用计数,以便它具有销毁自己实例(和拥有的内存)的固有知识。

例如,使用IMalloc(请参见MSDN)进行实例分配和放置new是另一种方法(在之前调用OleInitialize)...

事实上,内存分配是一个问题,具体取决于正在使用的内容。例如,在应用程序的不同部分中混合静态链接CRT和动态链接CRT将导致问题,就像混合调试和发布代码一样。问题在于用于释放内存的代码使用了不同的“内存管理器”。但是,如果抛出的对象知道自己的销毁方式,那么就没问题,因为dtor代码将驻留在分配它的编译单元中。


为什么需要IMalloc?如果按照您的建议通过new抛出动态分配的引用计数异常对象,只要new/delete成对在DLL代码中完成,那么为什么不能足以正常工作呢? - Paul J. Lucas
@Paul J. Lucas:这就是为什么在我提到IMalloc的句子中说“会有另一种方式”的原因。如果不清楚,对不起。英语不是我的母语。我会将其移动到单独的段落中... - 0xC0000022L

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