今天遇到一个很奇怪的问题,就是调用函数释放内存时程序崩溃,仔细了解才发现时运行时库链接方式对内存的影响。
现场描述:
A.exe中加载B.dll. 在A.exe中用new申请了一片内存,在B.dll中执行delete导致程序崩溃。
原因:
跨模块内存管理不一致导致。A.exe是MD链接,B.dll是MT链接,有2个C++运行库运行在同一个地址空间上,有可能读写同样内存区域,导致程序崩溃。
解决办法:
每个模块自己管理内存。不要跨模块申请/释放内存.
1. A.exe中调用B.dll中的对应的函数,由其分配内存,然后再调用B.dll中的函数去释放。
2. A申请的内存,由A来释放。
3.B.dll使用MD链接,而且B编译时使用和A编译时相同版本的运行时库。
详解:
在Windows系统中,进程和dll的内存管理是由Runtime Library实现的,而MT和MD影响了Runtim Library的链接方式,从而导致了此问题。
l MT 是多线程静态链接运行时库。
l MD是多线程动态链接运行时库。
在《》中专门讲述了该问题。我这里简单摘录一下:
If you choose to link with the static runtime library, then your module has its own private copy of the C/C++ runtime. When your module calls
new
ormalloc
, the memory can only be freed by your module callingdelete
orfree
. If another module callsdelete
orfree
, that will use the C/C++ runtime of that other module which is not the same as yours. Indeed, even if you choose to link with the DLL version of the C/C++ runtime library, you still have to agree which version of the C/C++ runtime to use. If your DLL usesMSVCRT20.DLL
to allocate memory, then anybody who wants to free that memory must also useMSVCRT20.DLL
.另外在MSDN《》也提到:
If you do choose to mix CRT libraries, remember that you have two separate copies of the CRT, with separate and distinct states, so you must be careful about what you try to do across a CRT-boundary. There are many ways to get into trouble with two CRTs. Here are just a few:
- There are two separate heaps. You cannot allocate (explicitly with new, malloc, or so on -- or implicitly with strdup, strstreambuf::str, or so on), and then pass the pointer across a CRT-boundary to be freed.
- You cannot pass a FILE* or file handle across a CRT-boundary and expect the "stdio low-level IO" to work.
- You cannot set the locale in one and expect the other's locale to be set.
简单点说,当使用静态库链接时,会有多份运行时库,而且每份库都拷贝一份自己的内存管理。而使用动态链接后,由于都是链接的同一个运行时库,这样就保证内存管理只用一份了。但是动态链接时必须两个模块使用同样版本的运行时库,不同版本的依旧会有多份。
在该案例中,由于A.exe是MD动态链接,B.dll是MT静态链接,导致有2份运行时库存在,在进行内存管理时就出错了,从而导致程序崩溃。
所以跨模块的内存管理,{zh0}是由每个模块提供自己的分配和销毁接口函数,然后在模块外部通过这些接口的调用来控制对象的生命期,而不是在外部 new/delete。如果实在要用,可以使用微软提供了GlobalAlloc/GlobalFree这样的全局内存API,用它们的话跨模块也没有问题的。
扩展阅读:
1.