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

Posted

技术标签:

【中文标题】为啥这个程序会崩溃:在 DLL 之间传递 std::string【英文标题】:Why does this program crash: passing of std::string between DLLs为什么这个程序会崩溃:在 DLL 之间传递 std::string 【发布时间】:2011-01-20 07:04:52 【问题描述】:

我无法弄清楚以下崩溃的原因 (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(或可执行文件)之间传递字符串的安全方法是什么?不使用带有自定义删除器的 shared_ptr 之类的包装器。

【问题讨论】:

相关:***.com/q/5347355/103167 【参考方案1】:

确保两个项目(App 和 Dll)都使用“多线程 DLL”运行时库之一,而不是静态版本。

属性 --> C/C++ --> 代码生成--> (/MD or /MDd强>)

注意: 如果您在应用程序中使用任何第三方库,那么您可能还需要重新编译这些库,链接器通常会通过不匹配/重复运行时错误通知您。

【讨论】:

【参考方案2】:

这实际上并不是由不同的堆实现引起的——MSVC std::string 实现不为那么小的字符串使用动态分配的内存(它使用小字符串优化)。 CRT 确实需要匹配,但这次不是你想要的。

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

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

那么发生了什么?变量s 使用复制构造函数的调试版本进行初始化,以复制std::string 的发布版本。版本之间成员变量的偏移量不同,所以你复制垃圾。 MSVC 实现有效地存储了开始和结束指针——您已将垃圾复制到其中;因为它们不再为 null,所以析构函数会尝试释放它们,但您会遇到访问冲突。

即使堆实现相同,它也会崩溃,因为您正在释放指向一开始从未分配过的内存的垃圾指针。


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

【讨论】:

你怎么能找到要求编译器给你一个类的大小(以位为单位)?【参考方案3】:

除了上面所说的之外,请确保 Platform Toolset(在 Properties->General 下)在两个项目中是相同的。否则到达端的字符串内容可能是假的。

当具有 v100 工具集版本的控制台应用程序项目使用设置为 v90 的库时,我发生了这种情况。

【讨论】:

【参考方案4】:

您需要为应用程序中的每个 DLL 链接到相同的运行时库(DLL 库),无论是调试还是发布,其中内存分配在另一个中并在另一个中释放。 (使用动态链接的运行时库的原因是,整个进程都会有一个堆,而不是每个链接到静态的 dll/exe 堆。)

这包括按值返回 std::string 和 stl-containers,这就是你所做的。

原因有两个(更新部分)

这些类具有不同的布局/大小,因此不同编译的代码假定数据位于不同的位置。先创建它的人是对的,但另一个人迟早会导致崩溃。 每个运行时库中的 msvc 堆实现都不同,这意味着如果您尝试释放堆中未分配它的指针,它将变得疯狂。 (如果布局相似,即您在第一种情况下的寿命更长,则会发生这种情况。)

所以,直接使用你的运行时库,或者停止在不同的 dll 中释放/分配(即停止按值传递内容)。

【讨论】:

不解释就投反对票? ..我想知道怎么了。 您的回答表明崩溃是由不匹配的内存分配函数引起的,但实际上是由不匹配的 std::string 定义引起的。 好的。谢谢。在这种情况下,可能是对象的大小不同,但如果 std::string 在调试/发布中具有类似的布局,那么堆分配无论如何都会咬他。【参考方案5】:

可能是字符串 A::getString() 返回正在 在 A.dll 中分配并在 main.exe?

是的。

如果是这样,为什么 - 以及什么是安全的 在 DLL 之间传递字符串的方法(或 可执行文件,就此而言)?没有 使用 shared_ptr 之类的包装器 自定义删除器。

对我来说,使用shared_ptr 听起来很明智。请记住,根据经验,分配和释放应该由同一个模块完成,以避免出现此类故障。

跨 dll 导出 STL 对象充其量是一个棘手的问题。我建议您先查看this MSDN 知识库文章和this 帖子。

【讨论】:

感谢文章链接+1 嗯。如果类的布局在 DLL 之间不同,这真的行不通吗?尝试访问该类仍然会失败。 (释放/释放 shared_ptr 和/或传回 DLL 将起作用,但尝试使用它不会)【参考方案6】:

这可能是因为 DLL 和 EXE 是使用不同的 CRT 设置编译的。所以当你传递一个字符串时,会发生一些资源冲突。检查 DLL 和可执行文件的项目设置。

【讨论】:

我特意选择了这些不同的设置来测试这种配置中的缺陷所在。这个字符串问题让我感到疑惑,因为我认为内存管理将完全发生在每个 DLL 或 EXE 中。

以上是关于为啥这个程序会崩溃:在 DLL 之间传递 std::string的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个程序崩溃

为啥注入的任何 DLL 都会使主机进程崩溃?

我可以将 std::string 传递给 DLL 吗?

为啥我不能将 lambda 传递给这个需要 std::function 的函数? [复制]

为啥在 dlopen'd 函数中传递的 std::any 的 std::any_cast 会引发错误

为啥这个递归会崩溃?