主程序和dll中的内存的分配和释放不是由相同的堆管理程序完成的,例如动态链接库中的堆在默认情况下是由msvcrt.dll中的堆管理程序管理的 (以动态链接的方式),而exe程序的堆在默认情况下是由程序自己的代码管理(以静态链接的方式)。
如果程序采用静态链接的方式,每一个可执行模块都会创建自己的CRT堆,在模块内部进行new和delete操作的时候,是在CRT堆上进行的,这样不会出错。但是如果在dll模块中申请存储,但是在主程序模块释放存储,就会出现不知道如何正确释放dll中分配的内存或者释放存储的行为是错误的。
首先CRT就是C RunTime的缩写,意思是C运行库。CRT可以理解为windows操作系统对C语言提供的一套支撑库,使得C程序通过C标准库函数就能与操作系统交互,而不需要调用windows API。普通CRT进程退出时,会首先以LIFO调用atexit注册的例程,执行完这些例程后,跟着清理CRT堆空间(而不是清理进程堆空间),接着调用ExitProcess退出。注意了,不要以为进入ExitProcess后,进程就退掉,其实在进入ExitProcess后,将调用已加载的Dll的DllMainCRTStartup,这个函数以Process_Detach调用加载到进程的Dll的DllMain函数,之后,还会调用Dll的atexit注册例程,之后又是清理Dll自身的CRT堆空间。CRT堆就供new和malloc分配内存的进程堆空间。一个重要的概念,MainCRTStartup(DllMainCRTStartup)会调用main(DllMain)、atexit序列及清理CRT堆空间(静态连接为前提)。
其实,每个Dll模块都有自己的atexit stack,这个stack只有在Process_Detach时才会调用,每个Dll的atexit stack单独维护,并且与进程的atexit stack毫无关系。所以,请注意,凡是Dll内注册的atexit,不要引用非自身模块的变量或者非自身模块的CRT堆空间(静态连接为前提),因为,Dll的atexit例程调用序列只按照自身Dll注册的序列调用,与外部的atexit序列不会产生任何关系。而引用的外部变量,很可能在Dll调用atexit注册的例程时已无效,特别是不要引用非自身CRT堆空间(静态连接为前提)。
CRT堆是我们使用new、malloc的基础,CRT堆在可执行模块的入口点(MainCRTStartup、DllMainCRTStartup)使用HeapCreate创建。基于win32虚地址机制,CRT堆对象的地址与进程默认堆是在同一个线性地址空间内,使得进程可以通过加减内存地址轻松访问。
我们现在知道CRT堆的由来,那么问题来了,当可执行模块是静态连接(或连接到不同版本的CRT运行库)的时候,这份创建CRT堆的代码会存在于各个的可执行模块内部,于是,每个可执行模块在加载时都会创建自己的堆,我把这种堆称为自身CRT堆。
当这些拥有自身CRT堆的可执行模块加载到同一个进程空间内的时候,它们的堆空间虽然能交叉访问,但却不能交叉释放(A模块分配的内存不能在B模块内释放),因为这些内存不是属于同一个堆的。同样,A模块分配的内存在A模块卸载后就变得无效了,因为A模块卸载会释放A模块自身CRT堆。当使用STL容器作模块间传递时,这些问题特别容易显现。想象一下容器内的元素来自不同的CRT堆,当容器析构时是怎么样的情况。
所以,当应用程序以可执行模块划分的时候,应该统一使用动态连接CRT运行库,并且,连接同一个版本的运行库。那么,什么是同一个版本的运行库呢?下一节展开分析。
话说回来,如果所有堆操作都使用进程默认堆,就不会出现问题了。不过,使用默认堆空间效率会比CRT堆低,因为CRT堆会预先保留提交内存以备使用,另外,使用进程默认堆,就不能使用new、delete创建销毁对象了。离开new,如何创建C++对象?^_^,使用placement new嘛。如何delete对象?先显式析构,再HeapFree吧。
http://www.cnblogs.com/rickerliang/archive/2011/09/14/2176656.html