跨共享库分配和释放内存

7

当使用Windows dll时,我们应该限制内存分配/释放在dll边界内,因为dll可能正在使用它自己的堆。因此,我们需要导出分配器和从dll中释放函数。

IsomeInterface* getObject();
void freeObject(IsomeInterface *object);

这样,对象的创建和删除将位于dll内部。

在linux上共享库(.so)也存在这个问题吗?在处理共享库时,我们是否也需要注意保持分配/释放在共享库内部。 我尝试了以下快速测试,在linux上可以工作。相同的例子在Windows DLL上不起作用(如果exe和dll都编译为/ MD以利用相同的堆,则Windows DLL将起作用)。

std::vector<int> vec;
vec.push_back(42);
PassAVector(vec);

PassAVector 存在于一个共享库中。

void PassAVector(std::vector<int> &vec)
{
     vec.push_back(1);  // These would cause reallocation
     vec.push_back(2);
     vec.push_back(3);
     vec.push_back(4);
     vec.push_back(5);
}

这是否意味着在unix上共享库与可执行文件共享堆(相当于windows上的/MD开关)?

是否可以通过在Linux上编译(一些编译器标志)共享库(.so)或可执行文件,使它们开始使用不同的堆(类似于Windows的/MT开关),并出现此问题?

编辑:找到这个,似乎表明跨边界传递STL是可以的,只要编译器是gcc。


PassAVector 遭遇了一个不同的问题,与操作系统无关:C++ 没有标准 ABI。在模块边界传递 C++ 对象从来没有得到支持。 - IInspectable
@IInspectable 即使两个模块都使用相同的编译器版本和标志,并且使用相同的堆,也不行吗? - user3819404
如果你使用完全相同的编译器和编译器选项编译API的两端,那么跨越C++对象是安全的(假设它们在跨模块边界时也同意分配器)。但由于你正在编译一个库,你无法控制调用者,所以一切都不确定。 - IInspectable
@IInspectable 同意。不同的编译器(或版本)意味着两个模块看到的std::vector实现不同,因此一切都无法预测。在这里,我特别想了解堆的情况。在Windows上,模块可以有不同的堆,即使使用相同的编译器/配置,如果分配跨越边界,也无济于事。对于gcc而言,堆的情况如何? - user3819404
我不熟悉Linux中共享对象的实现细节,因此无法对此发表评论。在Windows上,解决此问题的一个成熟方案是COM(或其后继者Windows Runtime)。这使您可以自由地在模块边界(或进程或计算机)之间交换对象。 - IInspectable
自从VC++2017开始,使用Windows不再需要此做法,因为您可以编译成通用C运行时库(UCRT),详情请见https://devblogs.microsoft.com/cppblog/introducing-the-universal-crt/。这样,您甚至可以混合使用调试版和发布版的二进制文件,而至少在内存分配方面一切都能正常工作。 - Alois Kraus
2个回答

4
只要您坚持使用Glibc或其他“常规”分配器(jemalloc、tcmalloc等),堆状态将由所有库共享,因此您可以在任何地方释放使用malloc分配的内存。
理论上可能会绕过这个限制。例如,某个库可能链接了自定义实现的malloc/free(通过-Bsymbolic的符号脚本技巧),它有自己的私有堆,因此无法与程序的其他部分良好交互。但我从未在现实生活中见过这样的情况。
STL容器基于malloc/free,因此也可以跨库边界传递/修改它们。当然,不同的库可能使用不同的编译器和不兼容的STL版本(如libstdc++,libcxx等),但它们的C++容器类型将不同,编译器根本不会允许您将它们传递到不兼容的模块中。

1
在C++中改变堆的常规方法是重载全局操作符new/delete。
在Windows中,该重载仅限于特定的dll,因此会出现问题。
在Linux中,重载的全局操作符通常是真正的全局-因此第一个获胜,但如果您真的想要其中的特定堆,则可以实现它(然后您需要导出分配和释放函数): C++自定义全局new/delete覆盖系统库

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