文章结构:
前言及问题的抛出
(1)问题描述[关于两个默认堆的产生]
(2)微软警告[来自微软的解释]
(3)原因分析[导致异常的原因分析]
(4)针对该问题的应对方案[结论]
前言及问题的抛出:
堆,我们都知道,是动态申请的空间,需要我们显示进行申请及释放资源。譬如说,我们调用了malloc,就要调用free,我们调用了new,就要调用delete。否则会产生内存泄露。
但是,你有没有想过,即便是我们正常地配对使用new和delete,也有可能会出现问题呢?此处,抛出我们今天需要讨论的问题:两个默认堆的问题。
(1)问题描述。首先说一下,什么情况下,会出现两个默认堆。我们可以参考如下博文:
http://hi.baidu.com/gensoft/item/4b80ea9aa520c5f629164716
总结之:当exe链接到静态C库,DLL连接到动态C库时,就会产生两个默认堆。此时在一个模块里面new,在另外一个模块里面delete,就会出现问题!
(2)其实,对于该问题,msdn中就已经指出警告:
该警告msdn的链接:
http://msdn.microsoft.com/zh-cn/library/cc438633(v=vs.71).aspx
(3)归根结底,产生该问题的根本原因是:模块之间使用的运行时库不一致!
我们应该知道,new和delete的调用,最后是会依赖于运行时库的实现的。
举例子说明一下:
试想一下,当A.exe链接到静态C库,B.DLL连接到动态C库时:
在A.exe的进程空间中,调用new函数,申请一段空间SAPCE。实际上是用静态C库提供的接口进行资源的申请的。如果这个时候,A.exe加载了B.DLL,并且将上述空间SAPCE的指针传给B.DLL,在B.DLL内,执行delete(SPACE)。那么,实际上执行的,是动态C库的delte接口,进行资源的释放!!!
到现在,我们是不是都觉察到这是怎么一回事了吧。
很明显,静态C库的new和delete是应该配对使用的,动态C库的new和delete也是应该配对使用的。不配对使用,虽然还没深入了解两者实现的具体不一致之处,我们也可以用拍一下脑袋,这不配对,带来的结果就是未知!未知,意味着结果不可控。不可控,啥都有可能发生!堆栈被破坏啦,程序崩溃了等等,让你找不着北。
(4)结论:
结论一:我们可以乖乖地遵循微软的警告,不要混合使用运行时库的静态版本和动态版本。
结论二:(嘻嘻,偏偏不遵循警告的做法,用一下我老大教我的法子)
上述问题的出现,目前看到的,主要是由于运行时库版本不同,却混合使用,然后在资源申请和释放阶段出问题。那么,我们可以切入出现问题的地方! 譬如:就上述例子而言,对于A.exe,可以申请一段空间SPACE,同时,A.exe可以提供一个函数,该函数负责释放该段空间SPACE!这样的话,B.dll在释放空间时,调用A.exe提供的这个函数即可。那么,SAPCE的申请和释放,使用的new和delete就是调用的一直的运行时库版本。
--------------------------------------------------------------------------------------------
(5)补充另外一位仁兄提供的网络上的资料,对该问题的描述更加详细到位:
一个模块一个堆,一个线程一个栈。
dll里malloc的内存,在exe里free会出错。
CRT(C运行时期库)不是使用进程缺省的堆来实现malloc(new中调用malloc)的,而是使用一个全局句柄HANDLE _crtheap来分配内存的。这个_crtheap是在XXXCRTStartUp(CRT提供的进口点函数)中创建的。
由于CRT静态连接,则楼主的DLL里有也有一个CRT,因此也有一个_crtheap。而在dll中的new使用dll中的_crtheap句柄分配堆,在exe中的delete使用exe中的_crtheap释放堆,当然失败!
1。在DLL中输出一个函数给EXE调用,专门用来释放由DLL分配的内存;
2。用GlobalAlloc()代替new,用GlobalFree()代替delete;
3。使用单一的堆,分配内存使用HeapAlloc(GetProcessHeap(),0,size),释放内存使用HeapFree(GetProcessHeap(),0,p);
4。把dll和exe的Settings的C/C++选项卡的Code Generation的Use Run-time liberary改成Debug Multithreaded DLL,在Release版本中改成Multithreaded DLL;这样使用一个CRT了——MSVCRT.DLL。
| C语言 | C++语言 | Windows 平台 | COM | IMalloc 接口 | BSTR |
申请 | malloc() | new | GlobalAlloc() | CoTaskMemAlloc() | Alloc() | SysAllocString() |
重新申请 | realloc() | | GlobalReAlloc() | CoTaskRealloc() | Realloc() | SysReAllocString() |
释放 | free() | delete | GlobalFree() | CoTaskMemFree() | Free() | SysFreeString() |
以上这些函数必须要按类型配合使用(比如:new 申请的内存,则必须用 delete 释放)。在 COM 内部,当然你可以随便使用任何类型的内存分配释放函数,但组件如果需要与客户进行内存的交互,则必须使用上表中的后三类函数族。IMalloc 接口又是对 CoTaskXXX() 函数族的一个包装。包装后,同时增强了一些功能,比如:IMalloc::GetSize()可以取得尺寸,使用 IMallocSpy 可以监视内存的使用。
------------------------------------------------------------------------------------------------------
OK,结束。希望有问题大家不吝指出,大家共同进步哈!
(读者请注意,尽信书不如无书。上述描述还不够深入且未贴近真相。哥要再研究研究运行时库实现、堆实现等相关知识后,再持续补充、纠正!!!)