﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-Violetrays</title><link>http://www.cppblog.com/Violetrays/</link><description /><language>zh-cn</language><lastBuildDate>Thu, 09 Apr 2026 03:18:48 GMT</lastBuildDate><pubDate>Thu, 09 Apr 2026 03:18:48 GMT</pubDate><ttl>60</ttl><item><title>[原创]C/C++运行期堆栈</title><link>http://www.cppblog.com/Violetrays/archive/2010/11/19/134077.html</link><dc:creator>紫光</dc:creator><author>紫光</author><pubDate>Fri, 19 Nov 2010 04:59:00 GMT</pubDate><guid>http://www.cppblog.com/Violetrays/archive/2010/11/19/134077.html</guid><wfw:comment>http://www.cppblog.com/Violetrays/comments/134077.html</wfw:comment><comments>http://www.cppblog.com/Violetrays/archive/2010/11/19/134077.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/Violetrays/comments/commentRss/134077.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Violetrays/services/trackbacks/134077.html</trackback:ping><description><![CDATA[&nbsp; <span>在《</span><span>windows</span><span>核心编程》中看到这样一段代码：</span> <span>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 71.3%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; HEIGHT: 216px; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;EXE模块</span><span style="COLOR: #008000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #000000">VOID&nbsp;EXEFunc()<br><img id=Codehighlighter1_24_64_Open_Image onclick="this.style.display='none'; Codehighlighter1_24_64_Open_Text.style.display='none'; Codehighlighter1_24_64_Closed_Image.style.display='inline'; Codehighlighter1_24_64_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_24_64_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_24_64_Closed_Text.style.display='none'; Codehighlighter1_24_64_Open_Image.style.display='inline'; Codehighlighter1_24_64_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top></span><span id=Codehighlighter1_24_64_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_24_64_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;PVOID&nbsp;pv&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;DLLFunc();<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;free(pv);<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</span></span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;DLL模块</span><span style="COLOR: #008000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #000000">PVOID&nbsp;DLLFunc()<br><img id=Codehighlighter1_92_119_Open_Image onclick="this.style.display='none'; Codehighlighter1_92_119_Open_Text.style.display='none'; Codehighlighter1_92_119_Closed_Image.style.display='inline'; Codehighlighter1_92_119_Closed_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align=top><img id=Codehighlighter1_92_119_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_92_119_Closed_Text.style.display='none'; Codehighlighter1_92_119_Open_Image.style.display='inline'; Codehighlighter1_92_119_Open_Text.style.display='inline';" src="http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif" align=top></span><span id=Codehighlighter1_92_119_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"></span><span id=Codehighlighter1_92_119_Open_Text><span style="COLOR: #000000">{<br><img src="http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;(malloc(</span><span style="COLOR: #000000">100</span><span style="COLOR: #000000">));<br><img src="http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</span></span></div>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 书中说，进程的单个地址空间由一个可执行模块和若干个</span><span>DLL</span><span>模块组成。这些模块中，有些可以链接到静态的LIB版本的</span><span>C/C++</span><span>运行期库，有些可以链接到一个动态的</span><span>DLL </span><span>版本的</span><span>C/C++</span><span>运行期库，而有些模块（不然不是用</span><span>C/C++</span><span>编写的话）则根本不需要</span><span>C/C++</span><span>运行期库。</span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 比如这段代码中如果</span><span>EXE</span><span>和</span><span>DLL</span><span>都链接到</span><span>DLL</span><span>的</span><span>C/C++</span><span>运行期库，那么上面的代码将能够很好的运行。但是如果两个模块中的一个或者两个都链接到静态</span><span>C/C++</span><span>运行期库，那么对</span><span>free</span><span>函数的调用就会失败。</span>&nbsp;</p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span>书上也没说明具体到底错在哪里，我开始时不明白这个错误的原因，在网上搜索了一大堆的网页，都没查出个所以然出来。为了找到原因，自己写了一个控制台</span><span>EXE</span><span>和一个很简单的</span><span>DLL</span><span>模块来调试，</span><span>DLL</span><span>模块采用隐式链接。对于</span><span>DLL</span><span>模块，链接的时候选择多线程静态的</span><span>C/C++</span><span>运行期库会报错，提示导出符号无法识别，对于这个错误我也没深入研究，于是采用的是</span><span>DLL</span><span>版本的</span><span>C/C++</span><span>运行期库。对</span><span>EXE</span><span>模块，编译链接的时候既可以选择静态</span><span>C/C++</span><span>运行期库，也可以选择动态的</span><span>C/C++</span><span>运行期库。对于选择动态</span><span>C/C++</span><span>运行期库，程序能够正常的运行，而选择静态</span><span>C/C++</span><span>运行期库则运行失败。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>调试之前要注意到两个问题：首先，</span><span>EXE</span><span>模块的入口函数</span><span>_tmain</span><span>、</span><span>_tWinMain</span><span>是由</span><span>C/C++</span><span>运行期启动函数调用的，也就是</span><span>tmainCRTStartup</span><span>函数；</span><span>DLL</span><span>模块的入口函数</span><span>DllMain</span><span>也是由</span><span>C/C++</span><span>启动函数调用的，也就是</span><span>_DllMainCTRStartup</span><span>函数。其次，由于</span><span>malloc</span><span>和</span><span>free</span><span>操作的都是堆栈，出现错误那肯定与这两个函数的实现有关。抓住这两个问题去寻找</span><span>crt</span><span>的源代码</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>我用的平台是</span><span>VS2010</span><span>的，在</span><span>crt</span><span>目录下发现这么几个文件：</span><span>crt0.c</span><span>、</span><span>crtexe.c</span><span>、</span><span>crtdll.c</span><span>、</span><span>dllcrt0.c</span><span>。其中，</span><span>__tmainCRTStartup</span><span>函数存在于</span><span>crt0.c</span><span>、</span><span>crtexe.c</span><span>文件中，</span><span>_DllMainCRTStartup</span><span>函数存在于</span><span>crtdll.c</span><span>、</span><span>dllcrt0.c</span><span>文件中。在</span><span>smalheap.c</span><span>文件中找到了</span><span>malloc</span><span>和</span><span>free</span><span>的定义。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>由于不知道各模块运行的时候会调用哪个启动函数，所以我在</span><span>crt0.c</span><span>、</span><span>crtexe.c</span><span>的</span><span>_tmainCRTStartup</span><span>函数入口处都设置了断点，在</span><span>crtdll.c</span><span>、</span><span>dllcrt0.c</span><span>的</span><span>_DllMainCRTStartup</span><span>处也设置了断点。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>经过调试发现，编译链接</span><span>EXE</span><span>模块时，分别选择选择</span><span>DLL</span><span>版的</span><span>C/C++</span><span>运行期库和</span><span>LIB</span><span>版的</span><span>C/C++</span><span>运行期库，它们调用的启动函数不一样。选择</span><span>DLL</span><span>的</span><span>C/C++</span><span>运行期库会调用</span><span>crtexe.c</span><span>中的</span><span>__tmainCRTStartup</span><span>函数，选择</span><span>LIB</span><span>的</span><span>C/C++</span><span>运行期库会调用</span><span>crt0.c</span><span>中的</span><span>__tmainCRTStartup</span><span>函数。仔细对比，发现这两个文件中有些不同，在</span><span>crt0.c</span><span>中有个</span><span>_heap_init()</span><span>函数，字面意思是堆初始化。再查询</span><span>_heap_init()</span><span>函数的代码，</span><span>_crtheap = HeapCreate( 0, BYTES_PER_PAGE, 0 )</span><span>，而</span><span>_crtheap</span><span>就是模块中的全局变量，代表进程中要调用这个模块中的</span><span>malloc</span><span>和</span><span>free</span><span>函数要用到的堆。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>为什么选择静态的运行期库会有堆初始化，而选择动态运行期库却没有初始化？而且调试后得知，在编译链接</span><span>DLL</span><span>模块时选择动态的</span><span>C/C++</span><span>运行期库，同样不执行堆初始化操作。那么它是不是在别的地方初始化了？继续搜索文件，发现在</span><span>crtlib.c</span><span>文件中有个</span><span>__CRTDLL_INIT()</span><span>函数，其中就调用了</span><span>_heap_init()</span><span>，于是在</span><span>__CRTDLL_INIT()</span><span>函数入口处下断点，再次调试后发现，如果选择的是</span><span>DLL</span><span>版本的</span><span>C/C++</span><span>运行期库，则系统会先于其他函数调用</span><span>crtlib.c</span><span>文件中的</span><span>__CRTDLL_INIT()</span><span>函数</span><span>(</span><span>注意到此函数的参数，与</span><span>DllMain</span><span>的参数一样，首次调用时参数</span><span>dwReason</span><span>的值为</span><span>DLL_PROCESS_ATTACH)</span><span>，接着调用</span><span>_heap_init()</span><span>初始化堆，再接着调用其他</span><span>DLL</span><span>的</span><span>C/C++</span><span>运行期启动函数，最后调用</span><span>EXE</span><span>模块的</span><span>C/C++</span><span>启动函数，最后进入_tmain或者_tWinMain。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>我得出结论：编译链接的时候，不管是</span><span>EXE</span><span>模块还是</span><span>DLL</span><span>模块，如果选择的是</span><span>DLL</span><span>版本的</span><span>C/C++</span><span>运行期库，运行的时候，系统会将</span><span>msvcrt*.dll映射</span><span>到进程的地址空间，然后调用</span><span>__CRTDLL_INIT()</span><span>函数，后者会调用</span><span>_heap_init()</span><span>初始化一个堆栈(只有在首次被映射到进程地址空间后才调用)，由全局变量</span><span>_crtheap</span><span>标识。如果选择的是</span><span>LIB</span><span>版的</span><span>C/C++</span><span>运行期库，</span><span>_heap_init()</span><span>会硬编码到所在的模块中，当运行的时候，调用本模块内的</span><span>_heap_init()</span><span>函数初始化一个堆栈，也由全局变量</span><span>_crtheap</span><span>标识，不过这个堆栈只有本模块中malloc、free等函数可以调用。</span></p>
<p>&nbsp;<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>所以，如果我们在编译链接上述的</span><span>EXE</span><span>和</span><span>DLL</span><span>的时候，如果某一个模块选择了</span><span>LIB</span><span>版的</span><span>C/C++</span><span>运行期库，则会在多个模块内出现堆栈</span><span>(</span><span>由各模块中的全局变量</span><span>_crtheap</span><span>标识</span><span>)</span><span>，用某个模块中的</span><span>free</span><span>函数去释放另外一个模块中</span><span>malloc</span><span>的堆，就会出错了。因为在</span><span>free</span><span>的时候会调用</span><span>HeapValidate()</span><span>函数：</span></p>
<p><span>BOOL WINAPI HeapValidate(</span><span><span> </span>HANDLE hHeap,</span><span><span>&nbsp; </span>DWORD dwFlags,</span><span>&nbsp;LPCVOID lpMem</span><span>);</span></p>
<p><span>hHeap</span><span>是本模块的堆，</span><span>lpMem</span><span>是其他模块堆中的地址，调用这个函数最终导致失败。</span></p>
<img src ="http://www.cppblog.com/Violetrays/aggbug/134077.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Violetrays/" target="_blank">紫光</a> 2010-11-19 12:59 <a href="http://www.cppblog.com/Violetrays/archive/2010/11/19/134077.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>