总结下,需要注意的是对于多个模块的开发,确保该模块的malloc自己free就OK了。
引用自
https://zhuanlan.zhihu.com/p/20628410?refer=jilinxiaohuo
https://www.zhihu.com/question/45753516
单例模式是一种很简单常用的设计模式,常见的做法可能是这样:
Renderer& getInstance()
{
static Renderer renderer;
return renderer;
}
当然,这个代码在不支持static local variable thread-safe init的编译器上,是没有办法保证线程安全的,c++11标准已经规定static local variable只会被初始化一次了,然而vs2013还没有实现,vs2015里才支持了这条标准.不过这条代码在我们的程序里只有一个线程访问,所以也就不存在线程安全的问题.
然后有一天,写了点代码,程序崩溃了:
Renderer::getInstance().xxxx();
分析发现是Renderer里的一个成员变量m_pMap == NULL了,而在项目的其他地方,也有用这个xxxx()函数的地方,竟然没有问题.
那就在xxxx()里下个断点,然后看下this指针的值吧,第一次命中时this的值是0x0456adc4, 第二次命中的时候是0x074324ad(这个地址是我随便写的,反正就是这两次命中断点this的值不一样).这是怎么回事呢?为什么出现了两个Renderer的实例呢?
细心的我发现这两个this指向的Renderer实例并没有在同一个模块内,一个在a.dll里,一个在main.exe里!造成这种现象的原因,是因为开篇的那个getInstance()方法所在工程是一个静态库,然后main.exe工程和a.dll工程均链接了这个静态库,导致main.exe里和a.dll里都存在一个renderer实例.而我们这个renderer实例在使用前,要这样:
Renderer::getInstance().Create();
然后才可以初始化m_pMap, 崩溃在main.exe里那行代码之前,并没有调用Create(),所以导致m_pMap == NULL崩溃了.
问题到这里已经水落石出了,想办法弄成在两个模块里共用一个实例就可以了!问题解决.
当然,如果文章就这样结束了,未免太没劲儿了吧,顺便说说另一个大家经常忽略的事情.
在开发dll的过程中,总会有意无意的写出一些跨模块分配释放内存的代码,比如在A模块malloc了一块内存,在B模块free,然后导致崩溃.然后将编译器选项由/MT改为/MD就可以解决问题.为什么会出现这种问题呢?我们来看看malloc的实现(vs2013 crt):
__forceinline void * __cdecl _heap_alloc (size_t size)
{
if (_crtheap == 0) {
#if !defined (_CRT_APP) || defined (_DEBUG)
_FF_MSGBANNER(); /* write run-time error banner */
_NMSG_WRITE(_RT_CRT_NOTINIT); /* write message */
#endif /* !defined (_CRT_APP) || defined (_DEBUG) */
__crtExitProcess(255); /* normally _exit(255) */
}
return HeapAlloc(_crtheap, 0, size ? size : 1);
}
malloc最后会调用HeapAlloc来分配内存,msdn看看HeapAlloc的说明,
,对着msdn的说明可以知道,malloc用的heap handle是 _crtheap,这个_crtheap是个全局变量,那我们看看是什么时候给_crtheap赋值的吧.HeapAlloc Function
Allocates a block of memory from a heap. The allocated memory is not movable.
Syntax
LPVOID WINAPI HeapAlloc( __in HANDLE hHeap, __in DWORD dwFlags, __in SIZE_T dwBytes );
Parameters
hHeap A handle to the heap from which the memory will be allocated. This handle is returned by the HeapCreate or GetProcessHeap function.
![](https://i-blog.csdnimg.cn/blog_migrate/34d03b33f52efc2b5d32439255a9e9e9.png)
x *!*_crtheap*
ba w4 0f7ec190
int __cdecl _heap_init (void)
{
// Initialize the "big-block" heap first.
if ( (_crtheap = GetProcessHeap()) == NULL )
return 0;
return 1;
}
KERNELBASE!GetProcessHeap:
75415620 64a118000000 mov eax,dword ptr fs:[00000018h]
75415626 8b4030 mov eax,dword ptr [eax+30h]
75415629 8b4018 mov eax,dword ptr [eax+18h]
7541562c c3 ret
![](https://i-blog.csdnimg.cn/blog_migrate/8e092b6273b676fbf3f7972a4b2a37f4.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8bcf96f0fde2895f127ae7cf7dc397ef.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8995292cb9492b00c5a43d84f8ae763a.png)
/MT:
Causes your application to use the multithread, static version of the run-time library.
/MD:
Causes your application to use the multithread- and DLL-specific version of the run-time library.如果使用/MT,会使用静态版本的运行时库,每个模块里都会有一个_crtheap全局变量,_heap_init就会在每个模块里都被调用一次,而使用/MD则使用动态库版本的运行时库,整个进程里只有运行时库里才有一份_crtheap全局变量,在crt的dll加载的时候调用一次_heap_init.但是分析_heap_init的实现可知,就算调用多次,返回的依然是PEB里的那个堆句柄啊,也不会导致不同模块里的_crtheap有不同的值,那HeapFree和HeapAlloc使用的handle就是同一个,也不应该引起崩溃啊.事实的确如此,我也是在今天写这篇专栏用windbg分析的时候才发现这个问题.是我之前记错了还是crt的实现有变化呢?于是我查看了下vs2003的crt的_heap_init的实现:
int __cdecl _heap_init (
int mtflag
)
{
// Initialize the "big-block" heap first.
if ( (_crtheap = HeapCreate( mtflag ? 0 : HEAP_NO_SERIALIZE,
BYTES_PER_PAGE, 0 )) == NULL )
return 0;
#ifndef _WIN64
// Pick a heap, any heap
__active_heap = __heap_select();
if ( __active_heap == __V6_HEAP )
{
// Initialize the small-block heap
if (__sbh_heap_init(MAX_ALLOC_DATA_SIZE) == 0)
{
HeapDestroy(_crtheap);
return 0;
}
}
#ifdef CRTDLL
else if ( __active_heap == __V5_HEAP )
{
if ( __old_sbh_new_region() == NULL )
{
HeapDestroy( _crtheap );
return 0;
}
}
#endif /* CRTDLL */
#endif /* _WIN64 */
return 1;
}
问题到这里就分析完毕了。