前面的Windows内存体系(6)--跨模块内存分配释放文章解释了跨 MT 模块分配的内存相互释放为什么会崩溃的问题,本文介绍如何解决该问题。

一、问题描述

这篇文章主要介绍我们在实际开发中经常遇到的一个问题:

我们开发了一个DLL,该DLL的运行时库采用MT模式,在为该模块定义接口函数时,接口参数使用的是std::string类型。EXE程序调用DLL中的接口时遇到"Debug Assertioni Failed"错误的问题。

上面的错误提示表明触发了debug_heap.cpp文件中的一个调试断言(release模式下调用的是heap.cpp中的分配函数),该断言用于判断指针是否指向堆分配的内存块的第一块。在 release 模式下不会弹出这样的断言错误,程序可能会直接崩溃(崩溃相对来说还比较好排查),就怕出现其他不可预料的、难以排查的错误。

二、实例

现有DLLUser.exe调用DLL.dll中的TestFun函数,代码量非常小:

DLL.dllTestFun函数定义:

1
2
3
4
DLL_API void TestFun( std::string str)
{
return;
}

DLLUser.exe中调用TestFun函数:

1
2
3
4
5
6
7
8
9
int _tmain(int argc, _TCHAR* argv[])
{
std::string str = "test";

TestFun(str);

return 0;
}

上面的代码运行之后程序就会弹出错误断言。原因是std::string在进行值传参的过程中会执行一次深拷贝,即:在堆上分配内存块,拷贝“test”到内存块中,然后将临时形参std::string对象传递到 dll 中,dll 中的TestFun函数在作用域结束后对临时形参进行释放时就出现了错误,因为尝试在dll的crt堆中释放由在exe的crt堆中分配的内存块。

三、自定义std::allocator

通过上面问题的分析,加上前面几篇文章对 Windows 内存体系的介绍,我们不难想出解决方案,其中一种方案就是:让std::string统一在进程的默认堆上分配内存块,而不是在各个模块的crt堆上分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <windows.h>
#include <string>
#include <vector>

template <typename T>
class vm_allocator : public std::allocator<T> {
public:
typedef size_t size_type;
typedef T* pointer;
typedef const T* const_pointer;

template<typename _Tp1>
struct rebind {
typedef vm_allocator<_Tp1> other;
};

pointer allocate(size_type n, const void *hint = 0) {
UNREFERENCED_PARAMETER(hint);
void* pBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, n * sizeof(T));

return (pointer)pBuffer;
}

void deallocate(pointer p, size_type n) {
UNREFERENCED_PARAMETER(n);
if (p) {
HeapFree(GetProcessHeap(), 0, p);
}
}

vm_allocator() throw() : std::allocator<T>() {
}

vm_allocator(const vm_allocator &a) throw() : std::allocator<T>(a) {
}

template <class U>
vm_allocator(const vm_allocator<U> &a) throw() : std::allocator<T>(a) {
}

~vm_allocator() throw() {
}
};

typedef std::basic_string<char, std::char_traits<char>, vm_allocator<char> > mystring;

上面的代码使用自定义的内存分配器vm_allocator<char>定义了mystring类,我们只需要将TestFun函数接口中的std::string修改为mystring即可解决崩溃问题。

文章图片带有“CSDN”水印的说明:
由于该文章和图片最初发表在我的CSDN 博客中,因此图片被 CSDN 自动添加了水印。