1. 无论这些第三方库是静态库还是动态库,或者同时有静态库和动态库(可以同时使用),必须保证在生成这些库时,使用的C Runtime库是同一个版本(/MT, /MTd, /MD,或者/MDd)【如果是VC的话,在项目属性->配置属性->C/C++->Code Generate->Runtime Library中设置】。 否则,在编译可执行文件时,会出现重复定义的问题,这是因为C Runtime库的不同版本之间的冲突造成的。所以,如果你要提供一个第三方库给别人使用,最好提供使用了不同C Runtime库的多个版本(/MT, /MTd, /MD,或者/MDd),以供选择。我推荐使用/MD和/MDd,原因下面第4节中讲。
2. 一旦DLL的文件映像被映射到调用进程的地址空间中,DLL的函数就可以供进程中运行的所有线程使用。实际上,DLL几乎将失去它作为DLL的全部特征。 对于进程中的线程来说,DLL的代码和数据看上去就像恰巧是在进程的地址空间中的额外代码和数据一样。当一个线程调用DLL函数时,该DLL函数要查看线程的堆栈,以便检索它传递的参数,并将线程的堆栈用于它需要的任何局部变量。此外, DLL中函数的代码创建的任何对象均由调用线程所拥有,而DLL本身从来不拥有任何东西。
如你所知,可执行文件的全局变量和静态变量不能被同一个可执行文件的多个运行实例共享。Windows98能够确保这一点,方法是在可执行文件被映射到进程的地址空间时为可执行文件的全局变量和静态变量分配相应的存储器。Windows2000确保这一点的方法是使用第13章介绍的写入时拷贝 (copy-on-write)机制。DLL中的全局变量和静态变量的处理方法是完全相同的。当一个进程将DLL的映像文件映射到它的地址空间中去时,系统将同时创建全局数据变量和静态数据变量的实例。
必须注意的是,单个地址空间是由一个可执行模块和若干个DLL模块组成的。这些模块中,有些可以链接到静态版本的C/C++运行期库,有些可以链接到一个DLL版本的C/C++运行期库,而有些模块(如果不是用C/C++编写
的话)则根本不需要C/C++运行期库。许多开发人员经常会犯一个常见的错误,因为他们忘记了若干个C/C++运行期库可以存在于单个地址空间中。请看下面的代码:
VOID EXEFunc(){ PVOID pv = DLLFunc(); free(pv);}
PVOID DLLFunc() { return(malloc(100)); }
那么你是怎么看待这个问题的呢?上面这个代码能够正确运行吗?DLL函数分配的内存块是由EXE的函数释放的吗?答案是可能的。上面显示的代码并没有 为你提供足够的信息。如果EXE和DLL都链接到DLL的C/C++运行时库(/MD),那么上面的代码将能够很好地运行。 但是,如果两个模块中的一个或者两个都链接到静态C/C++运行期库(/MT),那么对free函数的调用就会失败(原因下面第三节解释)。 我经常看到编程人员编写这样的代码,结果都失败了。
有一个很方便的方法可以解决这个问题。当一个模块提供一个用于分配内存块的函数时,该模块也必须提供释放内存的函数。让我们将上面的代码改写成下面的样子:
VOID EXEFunc(){ PVOID pv = DLLFunc(); DLLFreeFunc(pv);}
PVOID DLLFunc(){ PVOID pv = malloc(100); return(pv);}
BOOL DLLFreeFunc(PVOID pv){ return(free(pv));}
上面这个代码是正确的,它始终都能正确地运行。当你编写一个模块时,不要忘记其他模块中的函数也许没有使用C/C++来编写,因此可能无法使用malloc和free函数进行内存的分配。应该注意不要在代码中使用这些假设条件。
另外,在内部调用malloc和free函数时,这个原则对于C++的new和delete操作符也是适用的。
3. 问题:
1.“如果两个模块中的一个或者两个都链接到静态C/C++运行期库,那么对free函数的调用就会失败”,为什么? 静态CRT library和动态CRT library有什么区别?
答:因为malloc/free, new/delete 都是调用HeapAlloc/HeapFree 来实现来实现内存分配是释放的。查看Windows的API可以看到,这两个函数都需要一个Heap的HANDLE做为参数。CRT 库采用了全局变量来保存这个HANDLE。如果是静态链接, CRT库的代码会链接到各个dll中去,也包括这个全局变量。也就是说,每个静态链接的dll都有一个自己的全局堆句柄,他们自己都在这个句柄上使用内存。当一个dll释放另外一个dll分配的内存时由于使用的堆句柄不一致于是出错。当使用动态链接时,有于每个dll 都是去调用CRT库的dll函数来分配和释放内存的,使用的是同一个句柄,所以就没有这个问题。
2. “DLL中函数的代码创建的任何对象均由调用线程所拥有,而DLL本身从来不拥有任何东西”, DLL中的全局变量怎么解释?由多个应用程序使用该DLL时,DLL中的全局变量需要加上同步机制吗?
答:不同进程之间对 dll 全局变量的访问操作系统可以区分的,平时大家共用一份,当某个进程改了某个全局变量时,操作系统会自动为这个进程生成一份这个变量的拷贝,(copy- on-write)让这个进程访问拷贝的内容而不会影响其它进程中这个变量的值,所以不需要手动做同步。
4. 总结
综上所述,要在同一个DLL中对变量进行内存分配和释放的根本原因在于:每个DLL都要保存一份全局变量和静态变量的拷贝;CRT库中将堆的句柄保存为一个全局变量,而堆中内存的分配和释放都要使用这个句柄。
如果可执行程序和DLL都使用动态链接的CRT库(/MD,/MDd),那么CRT库只初始化一次(在其DLL中),每次内存分配和释放也只在CRT的DLL库中进行,这样使用唯一一个堆的句柄,就不会导致错误。
如果有一个DLL(称为D1)使用静态链接的CRT库(/MT,/MTd),可执行程序也使用静态链接的,那么D1中有一个堆的句柄,可执行程序中也有一个堆的句柄,两者进行内存分配和释放时使用各自的堆的句柄。那么就必须保证使用D1分配的内存,必须由D1释放,否则就会出错。这也就是1中我推荐使用/MD的原因。
////////////////////////////////////////////////////////////////////////////////////////////////////
//文章被翻来覆去的。。。
//上面可能是原文,下面可能是转载人加上的,不过感谢他们的劳动成果,
//拿下来慢慢看~
////////////////////////////////////////////////////////////////////////////////////////////////////
1.因为 malloc/free, new/delete 都是调用 HeapAlloc/HeapFree 来实现来实现内存分配是释放的。查看 Windows 的 API 可以看到,这两个函数都需要一个 Heap 的 HANDLE 做为参数。CRT 库采用了全局变量来保存这个 HANDLE。如果是静态链接, CRT 库的代码会链接到各个 dll 中去,也包括这个全局变量。也就是说,每个静态链接的 dll 都有一个自己的全局堆句柄,他们自己都在这个句柄上使用内存。当一个 dll 释放另外一个 dll 分配的内存时由于使用的堆句柄不一致于是出错。当使用动态链接时,有于每个 dll 都是去调用 CRT 库的 dll 函数来分配和释放内存的,使用的是同一个句柄,所以就没有这个问题。
2. 不同进程之间对 dll 全局变量的访问操作系统可以区分的,平时大家共用一份,当某个进程改了某个全局变量时,操作系统会自动为这个进程生成一份这个变量的拷贝,(copy- on-write)让这个进程访问拷贝的内容而不会影响其它进程中这个变量的值,所以不需要手动做同步。
在网上找到一段文字,再结合adlay的解释基本上清楚了.
http://dev.csdn.net/author/houdy/9f4bb2dc376d437787032971ee0eff97.html
四.malloc/free
这两个函数是使用频率最高的两个函数,由于他们是标准C库中的一部分,所以具有极高的移植性。这里的"移植性"指的是使用他们的代码可以在不同的平台下编译通过,而不同的平台下的C Run-Time Library的具体实现是平台相关的,在Windows平台的C Run-Time Library中的malloc()和free()是通过调用Heap Memory API来实现的。值得注意的是C Run-Time Library拥有独立的Heap对象,我们知道,当一个应用程序初始化的时候,首先被初始化的是C Run-Time Library,然后才是应用程序的入口函数,而Heap对象就是在C Run-Time Library被初始化的时候被创建的。对于动态链接的C Run-Time Library,运行库只被初始化一次,而对于静态连接的运行库,每链接一次就初始化一次,所以对于每个静态链接的运行库都拥有彼此不同的Heap 对象。这样在某种情况下就会出问题,导致程序崩溃,例如一个应用程序调用了多个DLL,除了一个DLL外,其他的DLL,包括应用程序本身动态连接运行库,这样他们就使用同一个Heap对象。而有一个DLL使用静态连接的运行库,它就拥有一个和其他DLL不同的Heap 对象,当在其他DLL中分配的内存在这个DLL中释放时,问题就出现了。