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

2021-12-22 00:00:00 dll memory-management visual-c++ c++

我无法弄清楚以下崩溃的原因 (MSVC9):

I have some trouble figuring out why the following crashes (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 中释放?

Obviously (?) this is due to the different memory models for the executable and DLL. Could it be that the string A::getString() returns is being allocated in A.dll and freed in main.exe?

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

If so, why - and what would be a safe way to pass strings between DLLs (or executables, for that matter)? Without using wrappers like shared_ptr with a custom deleter.

推荐答案

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

发生的情况是您违反了一个定义规则,从而调用了未定义的行为.

What's happening is that you're invoking undefined behaviour by violating the One Definition Rule.

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

The release and debug builds will have different preprocessor flags set, and you'll find that std::string has a different definition in each case. Ask your compiler what sizeof(std::string) is - MSVC10 tells me that it's 32 in a debug build and 28 in a release build (this isn't padding - 28 and 32 are both 4 bytes` boundaries).

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

So what's happening? Variable s is initialized using the debug version of the copy constructor to copy a release version of std::string. The offsets of the member variables are different between the versions, so you copy garbage. The MSVC implementation effectively stores begin and end pointers - you've copied garbage into them; because they're no longer null, the destructor tries to free them and you get an access violation.

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

Even if the heap implementations were the same it would crash, as you're freeing garbage pointers to memory that was never allocated in the first place.

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

In summary: the CRT versions need to match but so do the definitions - including the definitions in the standard library.

相关文章