C/C++运行期堆栈【转】

C/C++运行期堆栈【转】

  (2011-07-29 00:48:22)
标签: 

c

 
在《 windows 核心编程》中看到这样一段代码:
C/C++运行期堆栈【转】// EXE模块
C/C++运行期堆栈【转】
VOID EXEFunc()
C/C++运行期堆栈【转】
{
C/C++运行期堆栈【转】   PVOID pv 
= DLLFunc();
C/C++运行期堆栈【转】   free(pv);
C/C++运行期堆栈【转】 }

C/C++运行期堆栈【转】
C/C++运行期堆栈【转】
// DLL模块
C/C++运行期堆栈【转】
PVOID DLLFunc()
C/C++运行期堆栈【转】
{
C/C++运行期堆栈【转】   
return (malloc(100));
C/C++运行期堆栈【转】 }

    书中说,进程的单个地址空间由一个可执行模块和若干个DLL模块组成。这些模块中,有些可以链接到静态的LIB版本的C/C++运行期库,有些可以链接到一个动态的DLL 版本的C/C++运行期库,而有些模块(不然不是用C/C++编写的话)则根本不需要C/C++运行期库。

    比如这段代码中如果EXEDLL都链接到DLLC/C++运行期库,那么上面的代码将能够很好的运行。但是如果两个模块中的一个或者两个都链接到静态C/C++运行期库,那么对free函数的调用就会失败。 

    书上也没说明具体到底错在哪里,我开始时不明白这个错误的原因,在网上搜索了一大堆的网页,都没查出个所以然出来。为了找到原因,自己写了一个控制台EXE和一个很简单的DLL模块来调试,DLL模块采用隐式链接。对于DLL模块,链接的时候选择多线程静态的C/C++运行期库会报错,提示导出符号无法识别,对于这个错误我也没深入研究,于是采用的是DLL版本的C/C++运行期库。对EXE模块,编译链接的时候既可以选择静态C/C++运行期库,也可以选择动态的C/C++运行期库。对于选择动态C/C++运行期库,程序能够正常的运行,而选择静态C/C++运行期库则运行失败。

    调试之前要注意到两个问题:首先,EXE模块的入口函数_tmain_tWinMain是由C/C++运行期启动函数调用的,也就是tmainCRTStartup函数;DLL模块的入口函数DllMain也是由C/C++启动函数调用的,也就是_DllMainCTRStartup函数。其次,由于mallocfree操作的都是堆栈,出现错误那肯定与这两个函数的实现有关。抓住这两个问题去寻找crt的源代码

    我用的平台是VS2010的,在crt目录下发现这么几个文件:crt0.ccrtexe.ccrtdll.cdllcrt0.c。其中,__tmainCRTStartup函数存在于crt0.ccrtexe.c文件中,_DllMainCRTStartup函数存在于crtdll.cdllcrt0.c文件中。在smalheap.c文件中找到了mallocfree的定义。

    由于不知道各模块运行的时候会调用哪个启动函数,所以我在crt0.ccrtexe.c_tmainCRTStartup函数入口处都设置了断点,在crtdll.cdllcrt0.c_DllMainCRTStartup处也设置了断点。

    经过调试发现,编译链接EXE模块时,分别选择选择DLL版的C/C++运行期库和LIB版的C/C++运行期库,它们调用的启动函数不一样。选择DLLC/C++运行期库会调用crtexe.c中的__tmainCRTStartup函数,选择LIBC/C++运行期库会调用crt0.c中的__tmainCRTStartup函数。仔细对比,发现这两个文件中有些不同,在crt0.c中有个_heap_init()函数,字面意思是堆初始化。再查询_heap_init()函数的代码,_crtheap = HeapCreate( 0, BYTES_PER_PAGE, 0 ),而_crtheap就是模块中的全局变量,代表进程中要调用这个模块中的mallocfree函数要用到的堆。

    为什么选择静态的运行期库会有堆初始化,而选择动态运行期库却没有初始化?而且调试后得知,在编译链接DLL模块时选择动态的C/C++运行期库,同样不执行堆初始化操作。那么它是不是在别的地方初始化了?继续搜索文件,发现在crtlib.c文件中有个__CRTDLL_INIT()函数,其中就调用了_heap_init(),于是在__CRTDLL_INIT()函数入口处下断点,再次调试后发现,如果选择的是DLL版本的C/C++运行期库,则系统会先于其他函数调用crtlib.c文件中的__CRTDLL_INIT()函数(注意到此函数的参数,与DllMain的参数一样,首次调用时参数dwReason的值为DLL_PROCESS_ATTACH),接着调用_heap_init()初始化堆,再接着调用其他DLLC/C++运行期启动函数,最后调用EXE模块的C/C++启动函数,最后进入_tmain或者_tWinMain。

    

我得出结论:编译链接的时候,不管是EXE模块还是DLL模块,如果选择的是DLL版本的C/C++运行期库,运行的时候,系统会将msvcrt*.dll映射到进程的地址空间,然后调用__CRTDLL_INIT()函数,后者会调用_heap_init()初始化一个堆栈(只有在首次被映射到进程地址空间后才调用),由全局变量_crtheap标识。如果选择的是LIB版的C/C++运行期库,_heap_init()会硬编码到所在的模块中,当运行的时候,调用本模块内的_heap_init()函数初始化一个堆栈,也由全局变量_crtheap标识,不过这个堆栈只有本模块中malloc、free等函数可以调用。

    所以,如果我们在编译链接上述的EXEDLL的时候,如果某一个模块选择了LIB版的C/C++运行期库,则会在多个模块内出现堆栈(由各模块中的全局变量_crtheap标识),用某个模块中的free函数去释放另外一个模块中malloc的堆,就会出错了。因为在free的时候会调用HeapValidate()函数:

BOOL WINAPI HeapValidate( HANDLE hHeap,  DWORD dwFlags, LPCVOID lpMem);

    hHeap是本模块的堆,lpMem是其他模块堆中的地址,调用这个函数最终导致失败。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值