登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

秒大刀 博客

好好学习 天天向上

 
 
 

日志

 
 
 
 

跨模块的内存管理  

2009-02-17 21:00:36|  分类: C/C++ |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

今天在调试的时候发现有一条delete指令将会导致堆错误“Invalid Address specified to RtlValidateHeap”,起初以为是野指针,但再三确认并不是这么简单的问题。网上搜了下意识到了这个问题的严重性,原来我在不知不觉间跨入了“跨Module内存管理”的沼泽。

MSDN有一篇文章《Allocating and freeing memory across module boundaries》我贴上来:

I'm sure it's been drilled into your head by now that you have to free memory with the same allocator that allocated it. LocalAlloc matches LocalFree, GlobalAlloc matches GlobalFree, new[] matches delete[]. But this rule goes deeper.

If you have a function that allocates and returns some data, the caller must know how to free that memory. You have a variety of ways of accomplishing this. One is to state explicitly how the memory should be freed. For example, the FormatMessage documentation explicitly states that you should use the LocalFree function to free the buffer that is allocated if you pass the FORMAT_MESSAGE_ALLOCATE_BUFFER flag. All BSTRs must be freed with SysFreeString. And all memory returned across COM interface boundaries must be allocated and freed with the COM task allocator.

Note, however, that if you decide that a block of memory should be freed with the C runtime, such as with free, or with the C++ runtime via delete or delete[], you have a new problem: Which runtime?

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 or malloc, the memory can only be freed by your module calling delete or free. If another module calls delete or free, 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 uses MSVCRT20.DLL to allocate memory, then anybody who wants to free that memory must also use MSVCRT20.DLL.

If you're paying close attention, you might spot a looming problem. Requiring all your clients to use a particular version of the C/C++ runtime might seem reasonable if you control all of the clients and are willing to recompile all of them each time the compiler changes. But in real life, people often don't want to take that risk. "If it ain't broke, don't fix it." Switching to a new compiler risks exposing a subtle bug, say, forgetting to declare a variable as volatile or inadvertently relying on temporaries having a particular lifetime.

In practice, you may wish to convert only part of your program to a new compiler while leaving old modules alone. (For example, you may want to take advantage of new language features such as templates, which are available only in the new compiler.) But if you do that, then you lose the ability to free memory that was allocated by the old DLL, since that DLL expects you to use MSVCRT20.DLL, whereas the new compiler uses MSVCR71.DLL.

The solution to this requires planning ahead. One option is to use a fixed external allocator such as LocalAlloc or CoTaskMemAlloc. These are allocators that are universally available and don't depend on which version of the compiler you're using.

Another option is to wrap your preferred allocator inside exported functions that manage the allocation. This is the mechanism used by the NetApi family of functions. For example, the NetGroupEnum function allocates memory and returns it through the bufptr parameter. When the caller is finished with the memory, it frees it with the NetApiBufferFree function. In this manner, the memory allocation method is isolated from the caller. Internally, the NetApi functions might be using LocalAlloc or HeapAllocate or possibly even new and free. It doesn't matter; as long as NetApiBufferFree frees the memory with the same allocator that NetGroupEnum used to allocate the memory in the first place.

Although I personally prefer using a fixed external allocator, many people find it more convenient to use the wrapper technique. That way, they can use their favorite allocator throughout their module. Either way works. The point is that when memory leaves your DLL, the code you gave the memory to must know how to free it, even if it's using a different compiler from the one that was used to build your DLL.

Published Friday, September 15, 2006 7:00 AM by oldnewthing
Filed under: Code
  

跨模块的内存管理,最好是由每个模块提供自己的分配和销毁机制,然后在模块外部通过这些接口的调用来控制对象的生命期,而不是随随便便的在外部new&delete。 当然微软提供了GlobalAlloc/GlobalFree这样的全局内存API,用它们的话跨模块也没有问题的。

具体到我手头的项目,由于反外挂和加壳的需求,最好是将所有的执行体做成一个exe而不是很多个dll。静态库的需求是迟早的,那就今天全换算了。全部换成lib链接后堆错误消失,成功。

对模块、内存这些概念的理解又深刻了点了。

 

 2009-02-25

 今天“Invalid Address specified to RtlValidateHeap”这个问题再次出现了,再次确认后发现项目的运行时库设置不一致也是导致该问题的重要原因。对于多个动态链接库的项目,应该将运行时库设置为多线程调试DLL(/MDd)或者多线程DLL(/MD),这可以保证多个动态链接库使用同一套运行时库进行内存管理。(参考:http://hi.baidu.com/umu618/blog/item/d4e8242e424e3e564fc226d3.html

另外若将解决方案中某项目以静态链接库的形式嵌入到多个项目,则可能造成运行时库的内存管理有多份,这样也会导致“Invalid Address specified to RtlValidateHeap”现象的出现。将这些核心的东西以dll的形式体现,就能保证全局内存管理ok了。看来win下做东西dll还是王道啊。

  评论这张
 
阅读(6873)| 评论(1)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018