为什么这个程序会崩溃:在DLL之间传递std::string

27

我在研究以下代码 (MSVC9) 为何会崩溃时遇到了一些麻烦:

//// the following compiles to A.dll with release runtime linked dynamically
//A.h
class A {
  __declspec(dllexport) std::string getString();
};
//A.cpp
#include "A.h"
std::string A::getString() {
   return "I am a string.";
}

//// the following compiles to main.exe with debug runtime linked dynamically
#include "A.h"
int main() {
   A a;
   std::string s = a.getString();
   return 0;
} // crash on exit

很明显,这是由于可执行文件和DLL的不同内存模型所致。是不是因为 A::getString() 返回的字符串在 A.dll 中分配,在 main.exe 中释放了?

如果是这样的话,为什么会这样 - 以及在 DLL(或者执行文件)之间传递字符串的安全方法是什么?而且不使用自定义 deleter 的 shared_ptr 包装器。


相关内容:https://dev59.com/G2435IYBdhLWcg3wjw3S - Ben Voigt
6个回答

58

这并不是由于堆的不同实现引起的 - MSVC std::string 实现对于那么小的字符串不使用动态分配内存(它使用小字符串优化)。CRT 需要匹配,但这次不是这个问题。

发生的情况是,您通过违反一个定义规则来引起未定义的行为。

发布版和调试版将设置不同的预处理器标志,您会发现在每种情况下,std::string具有不同的定义。询问您的编译器sizeof(std::string)是多少 - MSVC10 告诉我在调试版本中为 32,在发布版本中为 28(这不是填充 - 28 和 32 都是 4 字节的边界)。

那么到底发生了什么?变量s使用调试版本的复制构造函数来复制发布版本的 std::string。成员变量的偏移量在两个版本之间不同,所以你复制了垃圾。MSVC 实现有效地存储了 begin 和 end 指针 - 你把垃圾复制到了它们里面;因为它们不再为空,析构函数尝试释放它们,然后就会得到访问冲突错误。

即使堆的实现相同,它也会崩溃,因为你释放了从未分配的内存的垃圾指针。


总之:CRT 版本需要匹配,但定义也要匹配 - 包括标准库中的定义


你如何请求编译器给出一个类的位大小? - BlueTrin

4

A::getString()返回的字符串是在A.dll中分配并在main.exe中被释放吗?

是的。

如果是这样,为什么会这样 - 传递DLL(或可执行文件)之间的字符串的安全方法是什么?不使用具有自定义删除器的shared_ptr等包装器。

使用 shared_ptr听起来很明智。请记住,按照经验法则,为了避免出现类似问题,分配和释放应该由同一模块完成。

跨dll导出STL对象最多是一个棘手的问题。 我建议您首先查看 MSDN KB文章和 帖子。


感谢提供的文章链接 +1。 - mats
1
嗯,如果DLL之间的类布局不同,这并不会真正起作用?尝试访问该类仍将失败。(释放shared_ptr和/或传回DLL将起作用,但尝试使用它将不起作用) - Macke

3
您需要链接到相同的运行时库(即DLL库),无论是debug还是release版本,对于您应用程序中分配内存和在另一个DLL中释放内存的情况都需要这样做。(使用动态链接运行时库的原因是,这样就会有一个堆来为整个进程服务,而不是每个链接到静态库的dll / exe都有一个堆。)
这包括通过值返回std :: string和stl容器,因为这是您所做的。
原因有两个:
1.这些类具有不同的布局/大小,因此以不同方式编译的代码假定数据位于不同位置。谁先创建就是正确的,但另一个将迟早导致崩溃。
2.msvc堆实现在每个运行时库中都不同,这意味着如果您尝试在未分配它的堆中释放指针,它将变得疯狂。(如果布局相似,即您超过第一种情况的寿命,则会发生这种情况。)
因此,请确保您的运行时库正确,或者停止在不同的dll中释放/分配内存(即停止通过值传递内容)。

没有解释的踩?我想知道哪里出了问题。 - Macke
你的回答指出崩溃是由于内存分配函数不匹配引起的,但实际上是由于不匹配的std::string定义引起的。 - JoeG
好的。谢谢。在这种情况下可能是对象的大小不同,但如果std::string在调试/发布中具有类似的布局,则堆分配仍将使其受到影响。 - Macke

3
除了上面提到的内容外,请确保“平台工具集”(在属性->常规下)在两个项目中是相同的。否则,到达端的字符串内容可能是虚假的。
当使用v100工具集版本的控制台应用程序项目消耗设置为v90的库时,我曾遇到过这种情况。

2

可能是因为DLL和EXE使用不同的CRT设置进行编译。因此,当您传递字符串时,会发生某些资源冲突。请检查您的项目设置,包括DLL和可执行文件。


我故意选择了这些不同的设置来测试这个配置中可能存在的陷阱。这个字符串问题让我感到困惑,因为我认为内存管理应该完全在每个DLL或EXE中进行。 - mats

1
确保两个项目(App和Dll)都使用“多线程DLL”运行时库之一,而不是静态版本。
属性--〉C/C++--〉代码生成--〉(/MD或/MDd)
注意:如果您在应用程序中使用任何第三方库,则可能还需要重新编译这些库,链接器通常会通过不匹配/重复的运行时错误来通知您。

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