C++ 如何测试两个 DLL 是否共享同一堆(heap)?

10
众所周知,释放堆内存必须使用与分配它的分配器相同的分配器。在跨DLL边界交换堆分配对象时要考虑这一点。
一种解决方法是为每个对象提供一个析构函数,就像在C API中一样:如果一个DLL允许创建对象A,那么它将不得不提供一个名为"A_free"或类似的函数1
另一个相关的解决方案是将所有分配包装成shared_ptr,因为它们存储了链接到分配器的指针2
另一种解决方案是“注入”一个顶层分配器到所有加载的DLL(递归)中3
还有一种解决方案,就是不直接交换堆分配的对象,而是使用某种协议4
更安全的方法是确保DLLs共享同一堆,这应该(会?)发生在它们共享兼容的编译选项(编译器、标志、运行时等)时56。这似乎很难保证,特别是如果想使用软件包管理器而不是一次性构建所有内容。

有没有一种在运行时检查多个 DLL 之间实际上堆相同的方法,最好是跨平台的?

为了可靠性和便于调试,这似乎比希望应用程序立即崩溃并且不会悄悄地破坏东西更好。


你的解决方案#2根本不是解决方案。没有单一的二进制兼容定义“shared_ptr”。 - Ben Voigt
是的,我知道在DLL边界有两个重要的考虑点:ABI兼容性和“堆共享安全性”。我的理解是,拥有“堆共享安全性”意味着具有ABI兼容性,但相反则不成立,你确认吗? - Gabriel Devillers
2
不,完全没有,你忽略了遵循“一次定义规则”的要求。你可以有两个共享同一个分配器(可能由C库提供)的C++编译器,但是它们使用的C++标准库可能不兼容。更糟糕的是,std::shared_ptr不是标准布局类,因此不同的C++编译器看到相同的头文件时可能会产生不同的二进制布局。 - Ben Voigt
1个回答

8

我认为这里最大的问题在于“堆”的定义,这假定了它有一个独特的定义。

问题在于Windows有HeapAlloc,而C++通常使用“堆”来表示由::operator new分配的内存。这两个可以是相同的,不同的,子集,或部分重叠的。

两个DLL都可能用C++编写并使用::operator new,但它们都可能链接自己独特的版本。因此,对于上一个段落中的观察结果,可能会有多个答案。

现在让我们假设一个例子,其中一个DLL将::operator new直接转发到HeapAlloc,而另一个在调用HeapAlloc之前计算分配次数。显然,两者不能正式混合使用,因为第二个分配器保留的计数会出错。但是这段代码非常简单,两个new可能都被内联。因此,在汇编级别上,你只需要调用HeapAlloc

即使你在运行时(!)反汇编代码,也没有办法检测到这一点-内联的计数器增量指令与周围的代码不可区分。


2
它们甚至可以都直接调用HeapAlloc(),但使用不同的堆句柄。 - Ben Voigt

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