﻿<?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++博客-yehao's Blog-文章分类-C/C++</title><link>http://www.cppblog.com/yehao/category/16570.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 13 Mar 2015 01:05:58 GMT</lastBuildDate><pubDate>Fri, 13 Mar 2015 01:05:58 GMT</pubDate><ttl>60</ttl><item><title>__declspec，uuid，__uuidof 使用说明</title><link>http://www.cppblog.com/yehao/articles/209979.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Mon, 09 Mar 2015 10:15:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/209979.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/209979.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/209979.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/209979.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/209979.html</trackback:ping><description><![CDATA[<span style="font-family: Arial; line-height: 26px; background-color: #ffffff;">用来获取 某种结构、接口及其指针、引用、变量 所关联的GUID，类似于某些语言中获取类型 typeof 这样的操作。</span><br style="font-family: Arial; line-height: 26px; background-color: #ffffff;" /><span style="font-family: Arial; line-height: 26px; background-color: #ffffff;">假定c++中，有结构体s</span><br style="font-family: Arial; line-height: 26px; background-color: #ffffff;" /><div bg_cpp"="" style="width: 936.53125px; line-height: 26px;"><div><div><strong>[cpp]</strong>&nbsp;<a href="http://blog.csdn.net/objectively/article/details/9629937#" title="view plain" style="padding: 1px; display: inline-block; width: 16px; height: 16px; text-indent: -2000px; background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_plain.gif); background-position: 0% 0%; background-repeat: no-repeat;">view plain</a><a href="http://blog.csdn.net/objectively/article/details/9629937#" title="copy" style="padding: 1px; display: inline-block; width: 16px; height: 16px; text-indent: -2000px; background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_copy.gif); background-position: 0% 0%; background-repeat: no-repeat;">copy</a><div style="position: absolute; left: 709px; top: 488px; width: 18px; height: 18px; z-index: 99;"></div></div></div><ol start="1"><li style="line-height: 18px;">struct&nbsp;s&nbsp;&nbsp;</li><li style="line-height: 18px;">{&nbsp;&nbsp;</li><li style="line-height: 18px;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #2e8b57; font-weight: bold;">int</span>&nbsp;i;&nbsp;&nbsp;</li><li style="line-height: 18px;">};&nbsp;&nbsp;</li></ol></div><span style="font-family: Arial; line-height: 26px; background-color: #ffffff;">可以通过下面的__declspec 给这个结构 关联一个GUID</span><br style="font-family: Arial; line-height: 26px; background-color: #ffffff;" /><div bg_cpp"="" style="width: 936.53125px; line-height: 26px;"><div><div><strong>[cpp]</strong>&nbsp;<a href="http://blog.csdn.net/objectively/article/details/9629937#" title="view plain" style="padding: 1px; display: inline-block; width: 16px; height: 16px; text-indent: -2000px; background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_plain.gif); background-position: 0% 0%; background-repeat: no-repeat;">view plain</a><a href="http://blog.csdn.net/objectively/article/details/9629937#" title="copy" style="padding: 1px; display: inline-block; width: 16px; height: 16px; text-indent: -2000px; background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_copy.gif); background-position: 0% 0%; background-repeat: no-repeat;">copy</a><div style="position: absolute; left: 709px; top: 655px; width: 18px; height: 18px; z-index: 99;"></div></div></div><ol start="1"><li style="line-height: 18px;">struct&nbsp;__declspec(uuid("93A1665E-C9FA-4147-AC3A-3CC855281AF8"))&nbsp;s;&nbsp;&nbsp;</li></ol></div><span style="font-family: Arial; line-height: 26px; background-color: #ffffff;">以后程序中使用该结构</span><br style="font-family: Arial; line-height: 26px; background-color: #ffffff;" /><div bg_cpp"="" style="width: 936.53125px; line-height: 26px;"><div><div><strong>[cpp]</strong>&nbsp;<a href="http://blog.csdn.net/objectively/article/details/9629937#" title="view plain" style="padding: 1px; display: inline-block; width: 16px; height: 16px; text-indent: -2000px; background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_plain.gif); background-position: 0% 0%; background-repeat: no-repeat;">view plain</a><a href="http://blog.csdn.net/objectively/article/details/9629937#" title="copy" style="padding: 1px; display: inline-block; width: 16px; height: 16px; text-indent: -2000px; background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_copy.gif); background-position: 0% 0%; background-repeat: no-repeat;">copy</a><div style="position: absolute; left: 709px; top: 768px; width: 18px; height: 18px; z-index: 99;"></div></div></div><ol start="1"><li style="line-height: 18px;">s&nbsp;a,&nbsp;*b,&nbsp;&amp;c;&nbsp;&nbsp;</li><li style="line-height: 18px;">__uuidof(s);&nbsp;&nbsp;</li><li style="line-height: 18px;">__uuidof(a);&nbsp;&nbsp;</li><li style="line-height: 18px;">__uuidof(b);&nbsp;&nbsp;</li><li style="line-height: 18px;">__uuidof(c);&nbsp;&nbsp;</li></ol></div><p style="margin: 0px; padding: 0px; font-family: Arial; line-height: 26px; background-color: #ffffff;"></p><div bg_cpp"="" style="width: 936.53125px; line-height: 26px;"><div><div><strong>[cpp]</strong>&nbsp;<a href="http://blog.csdn.net/objectively/article/details/9629937#" title="view plain" style="padding: 1px; display: inline-block; width: 16px; height: 16px; text-indent: -2000px; background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_plain.gif); background-position: 0% 0%; background-repeat: no-repeat;">view plain</a><a href="http://blog.csdn.net/objectively/article/details/9629937#" title="copy" style="padding: 1px; display: inline-block; width: 16px; height: 16px; text-indent: -2000px; background-image: url(http://static.blog.csdn.net/scripts/SyntaxHighlighter/styles/images/default/ico_copy.gif); background-position: 0% 0%; background-repeat: no-repeat;">copy</a><div style="position: absolute; left: 709px; top: 909px; width: 18px; height: 18px; z-index: 99;"></div></div></div><ol start="1"><li style="line-height: 18px;">REFCLSID,REFIID,CLSID,IID,GUID&nbsp;=&nbsp;__uuidof(x)&nbsp;&nbsp;</li></ol></div><p style="margin: 0px; padding: 0px; font-family: Arial; line-height: 26px; background-color: #ffffff;"></p><p style="margin: 0px; padding: 0px; font-family: Arial; line-height: 26px; background-color: #ffffff;">都能得到结构s关联的GUID：("93A1665E-C9FA-4147-AC3A-3CC855281AF8")</p><img src ="http://www.cppblog.com/yehao/aggbug/209979.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2015-03-09 18:15 <a href="http://www.cppblog.com/yehao/articles/209979.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>解决为什么wcout不能输出中文问题 </title><link>http://www.cppblog.com/yehao/articles/209933.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Thu, 05 Mar 2015 07:36:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/209933.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/209933.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/209933.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/209933.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/209933.html</trackback:ping><description><![CDATA[<div id="cnblogs_post_body"><div><p>转自http://www.cnblogs.com/moonz-wu/archive/2008/12/30/1365552.html</p><p>wprintf 和 wcout</p><p>这篇文章应该是[<a href="http://blog.vckbase.com/netsin/" target="_blank">netsin</a>]的成果，我勤快，记下来。<br />注：wprintf是C的标准库函数，但wcout不是C++的标准成员，C++中的 L"&#8230;&#8230;" 是宽字符，却未必是unicode字符，这与编译器实现相关。<br />[<a href="http://blog.vckbase.com/smileonce/" target="_blank">乾坤一笑</a>]说：为什么 C/C++ 语言把 L"xx" 定义为由实现决定的呢？这显然是为了 C/C++ 的普适性、可移植性。Bjarne 的观点认为，C++ 的方式是允许程序员使用任何字符集作为串的字符类型。另外，unicode 编码已经发展了若干版本了，是否能永久适合下去也不得而知。有关 unicode 的详细论述以及和其它字符集的比较，我推荐你看《无废话xml》。</p><p><br />以下两段代码的执行环境是 windows xp professional 英文版，编译器是 VS2005RTM。</p><p>// C<br />#include &lt;stdio.h&gt;<br />#include &lt;locale.h&gt;<br />int main( void )<br />{<br />    setlocale( LC_ALL, "chs" );<br />    //setlocale( LC_ALL, "Chinese-simplified" );<br />    //setlocale( LC_ALL, "ZHI" );<br />    //setlocale( LC_ALL, ".936" );<br />    wprintf( L"中国" );</p><p>    return 0;<br />}</p><p>// C++<br />#include &lt;iostream&gt;<br />#include &lt;locale&gt;<br />using namespace std;<br />int main( void )<br />{<br />    locale loc( "chs" );<br />    //locale loc( "Chinese-simplified" );<br />    //locale loc( "ZHI" );<br />    //locale loc( ".936" );<br />    wcout.imbue( loc );<br />    std::wcout &lt;&lt; L"中国" &lt;&lt; endl;</p><p>    return 0;<br />}</p><p>说明：别混合使用 setlocale 和 std::locale 。<br /><br />------------------------- <a href="http://blog.vckbase.com/bruceteen/archive/2005/11/15/14924.html#20060705">2006-07-05 记</a><a name="20060705"></a> -------------------------</p><p> "VC知识库"                        编码为：56 43 D6 AA CA B6 BF E2 00                            // ANSI编码<br />L"VC知识库" 在VC++               中编码为：56 00 43 00 E5 77 C6 8B 93 5E 00 00                   // (windows口中的unicode)编码<br />L"VC知识库" 在GCC（Dev-CPP4990） 中编码为：56 00 43 00 D6 00 AA 00 CA 00 B6 00 BF 00 E2 00 00 00 // 只是将ANSI编码简单的加0<br />L"VC知识库" 在GCC（Dev-CPP4992） 中编译失败，报 Illegal byte sequence</p><p>L"VC知识库" 在 Dev-CPP4992 中解决步骤为：<br />a. 将文件保存为 utf-8 编码                                          // utf-8 是unicode的其中一种，但和(windows口中的unicode)不一样<br />b. 去掉BOM头：用二进制编辑器（比如VC）去掉刚才utf-8文件的前三个字节 // Linux/UNIX并不使用BOM<br />c. 使用 gcc/g++ 编译运行</p><p>经过以上解决步骤，在 dev-cpp4992 中<br /> "VC知识库" 编码为： 56 43 E7 9F A5 E8 AF 86 E5 BA 93 00 // utf-8编码，注意不再是ANSI编码了，因此用 printf/cout 将输出乱码<br />L"VC知识库" 编码为： 56 00 43 00 E5 77 C6 8B 93 5E 00 00 // (windows口中的unicode)编码</p><p>补充：在mingw32中使用wcout和wstring需要加一些宏，比如<br />#define _GLIBCXX_USE_WCHAR_T 1<br />#include &lt;iostream&gt;<br />int main( void )<br />{<br />    std::wcout &lt;&lt; 1 &lt;&lt; std::endl;<br />}<br />可以编译通过，但无法Link通过，在网上google了一下，stlport说mingw32有问题，mingw32说是M$的c runtime有问题。<br /><br />------------------------- 2007-01-05 记<a name="20060705"></a> -------------------------<br />一个多字节字符串和宽字符字符串互相转化的事例<br />#define _CRT_SECURE_NO_WARNINGS // only for vc8<br />#include &lt;string&gt;<br />#include &lt;clocale&gt;<br />#include &lt;cassert&gt;<br />inline const std::string to_mbcs( const std::string&amp; src )<br />{<br />    return src;<br />}<br />const std::string to_mbcs( const std::wstring&amp; src )<br />{<br />    char* old_locale = _strdup( setlocale(LC_CTYPE,NULL) ); // 保存原来的locale<br />    setlocale( LC_CTYPE, "chs" ); // 设置当前locale为chs，这在非简体中文平台上不可缺少</p><p>    size_t count1 = wcstombs( NULL, src.c_str(), 0 ); // 计算新字符串长度<br />    std::string des( count1, ' ' );<br />    size_t count2 = wcstombs( &amp;des[0], src.c_str(), count1 ); // 转化<br />    assert( count1 == count2 );</p><p>    setlocale( LC_CTYPE, old_locale ); // 恢复到原来的locale<br />    free( old_locale );</p><p>    return des;<br />}<br />inline const std::wstring to_wcs( const std::wstring&amp; src )<br />{<br />    return src;<br />}<br />const std::wstring to_wcs( const std::string&amp; src )<br />{<br />    char* old_locale = _strdup( setlocale(LC_CTYPE,NULL) ); // 保存原来的locale<br />    setlocale( LC_CTYPE, "chs" ); // 设置当前locale为chs，这在非简体中文平台上不可缺少</p><p>    size_t count1 = mbstowcs( NULL, src.c_str(), 0 ); // 计算新字符串长度<br />    std::wstring des( count1, L' ' );<br />    size_t count2 = mbstowcs( &amp;des[0], src.c_str(), count1 ); // 转化<br />    assert( count1 == count2 );</p><p>    setlocale( LC_CTYPE, old_locale ); // 恢复到原来的locale<br />    free( old_locale );</p><p>    return des;<br />}</p><p>#include &lt;iostream&gt;<br />int main( void )<br />{<br />    using namespace std;</p><p>    cout &lt;&lt; to_mbcs("你好1") &lt;&lt; endl;<br />    cout &lt;&lt; to_mbcs(L"你好2") &lt;&lt; endl;</p><p>    const locale loc( "chs" );<br />    wcout.imbue( loc );<br />    wcout &lt;&lt; to_wcs("你好3") &lt;&lt; endl;<br />    wcout &lt;&lt; to_wcs(L"你好4") &lt;&lt; endl;<br />}<br /><br />------------------------- 2008-09-03 记<a name="20060705"></a> -------------------------<br />参见 《<a id="viewpost1_TitleUrl" href="http://blog.vckbase.com/bruceteen/archive/2006/09/27/22541.html">MBCS To Unicode</a> 》</p></div></div><div id="MySignature">将想法付诸于实践，借此来影响他人是一个人存在的真正价值</div><img src ="http://www.cppblog.com/yehao/aggbug/209933.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2015-03-05 15:36 <a href="http://www.cppblog.com/yehao/articles/209933.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C/C++中的日期和时间 TIME_T与STRUCT TM转换</title><link>http://www.cppblog.com/yehao/articles/200731.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Fri, 31 May 2013 13:11:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/200731.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/200731.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/200731.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/200731.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/200731.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 摘要：本文从介绍基础概念入手，探讨了在C/C++中对日期和时间操作所用到的数据结构和函数，并对计时、时间的获取、时间的计算和显示格式等方面进行了阐述。本文还通过大量的实例向你展示了time.h头文件中声明的各种函数和数据结构的详细使用方法。关键字：UTC（世界标准时间），Calendar Time（日历时间），epoch（时间点），clock tick（时钟计时单元）1．概念在C/C++中，对字符...&nbsp;&nbsp;<a href='http://www.cppblog.com/yehao/articles/200731.html'>阅读全文</a><img src ="http://www.cppblog.com/yehao/aggbug/200731.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2013-05-31 21:11 <a href="http://www.cppblog.com/yehao/articles/200731.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++内存管理</title><link>http://www.cppblog.com/yehao/articles/166885.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Thu, 01 Mar 2012 08:25:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/166885.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/166885.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/166885.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/166885.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/166885.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: [导语]内存管理是C++最令人切齿痛恨的问题，也是C++最有争议的问题，C++高手从中获得了更好的性能，更大的自由，C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨，但内存管理在C++中无处不在，内存泄漏几乎在每个C++程序中都会发生，因此要想成为C++高手，内存管理一关是必须要过的，除非放弃C++，转到Java或者.NET，他们的内存管理基本是自动的，当然你也放弃了自由和对内存的支配权，还...&nbsp;&nbsp;<a href='http://www.cppblog.com/yehao/articles/166885.html'>阅读全文</a><img src ="http://www.cppblog.com/yehao/aggbug/166885.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2012-03-01 16:25 <a href="http://www.cppblog.com/yehao/articles/166885.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Cdecl/stdcall函数调用内存模型(gcc 3.4.5) </title><link>http://www.cppblog.com/yehao/articles/166804.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Wed, 29 Feb 2012 09:18:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/166804.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/166804.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/166804.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/166804.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/166804.html</trackback:ping><description><![CDATA[<p><span style="font-size: 14pt;background: white; color: black; font-family: 宋体">Cdecl/stdcall在</span><span style="font-size: 14pt;background: white; color: black; font-family: 宋体">不同的编译器下实现有会所有不同,本人是在gcc 3.4.5下测试的。</span></p>
<p><span style="background: white; color: black; font-family: Verdana">&nbsp;</span></p>
<h1><span style="background: white; color: black; font-family: Verdana">1.<span style="font-family: 宋体">无局部变量</span></span></h1>
<p><span style="font-size: small"><span style="background: white; color: black; font-family: Verdana">a.</span><span style="background: white; color: black; font-family: 宋体">刚进入函数时</span><span style="background: white; color: black; font-family: Verdana">:</span></span></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-size: small"><span style="background: white; color: black; font-family: Verdana"><!--StartFragment -->&nbsp; <img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/ggggfjeicfh/EntryImages/20091215/1.jpg" /></span></span></p>
<p><span style="background: white; color: black; font-family: Verdana">
<p align="left"><span style="font-size: small"><span style="font-family: Times New Roman">b.</span><span style="font-family: 宋体">然后在函数体一开始执行了以下代码之后</span><span style="font-family: Times New Roman"><strong>:</strong></span></span></p>
<p align="left"><strong>&nbsp;</strong></p>
<p><strong><span style="font-size: 12pt; color: #ab341d; font-family: 'Courier New'">push&nbsp;ebp</span></strong></p>
<p><strong><span style="font-size: 12pt; color: #ab341d; font-family: 'Courier New'">mov&nbsp;&nbsp; ebp,esp</span></strong></p>
<p><strong><span style="font-size: 12pt; color: #ab341d; font-family: 'Courier New'">sub&nbsp;&nbsp; esp,0x8</span></strong></p>
<p align="left"><strong><span style="font-size: 12pt; color: #ab341d"><span style="font-family: Times New Roman">......</span></span></strong></p>
<p align="left"><strong></strong><strong></strong></p></span>
<p>&nbsp;</p>
<p align="left"><span style="font-size: 12pt; color: #ab341d"><!--StartFragment --><span style="color: #000000">&nbsp;<img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/ggggfjeicfh/EntryImages/20091215/2.jpg" /> </span></span></p>
<p><span style="font-size: 12pt; color: #ab341d"><span style="color: #000000">
<div style="border-top-width: 1pt; padding-right: 0cm; padding-left: 0cm; border-left-width: 1pt; border-left-color: black;background: white; border-bottom-width: 1pt; border-bottom-color: black; padding-bottom: 3pt; border-top-color: black; padding-top: 0cm; border-right-width: 1pt; border-right-color: black">
<p style="padding-right: 0cm; padding-left: 0cm;background: white; padding-bottom: 0cm; padding-top: 0cm; text-align: left" align="left"><span style="color: black; font-family: Verdana">c.</span><span style="background: white; color: black; font-family: 宋体">函数末尾执行</span><span style="font-family: Times New Roman"><strong>:</strong></span></p>
<p style="padding-right: 0cm; padding-left: 0cm;background: white; padding-bottom: 0cm; padding-top: 0cm; text-align: left" align="left">&nbsp;</p>
<p style="padding-right: 0cm; padding-left: 0cm;background: white; padding-bottom: 0cm; padding-top: 0cm; text-align: left" align="left"><span style="font-size: 12pt;background: white; color: #ab341d; font-family: 'Courier New'">leave</span></p>
<p style="padding-right: 0cm; padding-left: 0cm;background: white; padding-bottom: 0cm; padding-top: 0cm; text-align: left" align="left"><span style="font-size: 12pt;background: white; color: #ab341d; font-family: Verdana">ret</span></p>
<p style="padding-right: 0cm; padding-left: 0cm;background: white; padding-bottom: 0cm; padding-top: 0cm; text-align: left" align="left"><span style="background: white; color: black; font-family: Verdana">Leave</span><span style="background: white; color: black; font-family: 宋体">其实就是使</span><span style="background: white; color: black; font-family: Verdana">ESP+0xc,</span><span style="background: white; color: black; font-family: 宋体">更通用一些就是</span><span style="background: white; color: black; font-family: Verdana">EBP+0x4,ESP</span><span style="background: white; color: black; font-family: 宋体">指向</span><span style="background: white; color: black; font-family: Verdana">Ret EIP,</span><span style="background: white; color: black; font-family: 宋体">然后返回</span><span style="background: white; color: black; font-family: Verdana">,</span><span style="background: white; color: black; font-family: 宋体">最后由函数的调用者清理堆栈，如果是stdcall的话，则在函数内执行清理堆栈操作，再执行返回操作。</span></p>
<p style="padding-right: 0cm; padding-left: 0cm;background: white; padding-bottom: 0cm; padding-top: 0cm; text-align: left" align="left">&nbsp;</p>
<h1><span style="background: white; color: black; font-family: 宋体"><span style="font-size: large"><span style="font-family: 宋体">2.有</span><span style="font-family: 'Times New Roman'">N</span><span style="font-family: 宋体">个</span><span style="font-family: 'Times New Roman'">int</span><span style="font-family: 宋体">型局部变量</span></span></span></h1></div></span></span>
<p>&nbsp;</p>
<p align="left">&nbsp;</p>
<p align="left"><span style="font-size: 12pt; color: #ab341d"><span style="color: #000000"><span style="font-size: 10.5pt;background: white; color: black; font-family: Verdana">a.</span><span style="font-size: 10.5pt;background: white; color: black; font-family: 宋体">刚进入函数时:</span></span></span></p>
<p align="left">&nbsp;</p>
<p align="left"><span style="font-size: 12pt; color: #ab341d"><span style="color: #000000"><span style="font-size: 10.5pt;background: white; color: black; font-family: 宋体"><!--StartFragment -->&nbsp;<img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/ggggfjeicfh/EntryImages/20091215/3.jpg" /> </span></span></span></p>
<p align="left">&nbsp;</p>
<p align="left">&nbsp;</p>
<p align="left"><span style="font-size: 12pt; color: #ab341d"><span style="color: #000000">
<p align="left"><span style="font-size: 10.5pt;background: white; color: black; font-family: 宋体">b.<span style="font-family: 宋体">然后在函数体一开始执行了以下代码之后</span><span style="font-family: Times New Roman"><strong>:</strong></span></span></p>
<p align="left"><span style="font-size: 10.5pt;background: white; color: black; font-family: 宋体"><span style="font-family: Times New Roman"><strong></strong></span></span></p></span></span>&nbsp; 
<p>&nbsp;</p>
<p><span style="font-size: 10.5pt;background: white; color: black; font-family: 宋体"><span style="font-family: Times New Roman">
<p><span style="font-size: 12pt; color: #ab341d; font-family: 'Courier New'"><strong>push&nbsp;ebp</strong></span></p>
<p><span style="font-size: 12pt; color: #ab341d; font-family: 'Courier New'"><strong>mov&nbsp;&nbsp; ebp,esp</strong></span></p>
<p><span style="font-family: courier new,courier"><span style="font-size: small"><strong><span style="color: #993300">sub&nbsp;&nbsp; esp,( MIN(X)*0x10+0x8 );</span></strong><span style="color: #ab341d">满足</span><span style="color: #ab341d">:MIN(X)*0x10&gt;=N*0x4</span></span></span></p>
<p align="left"><span style="font-size: 12pt; color: #ab341d"><strong>......</strong></span></p>
<p align="left">&nbsp;</p></span></span>
<p>&nbsp;</p>
<p><span style="font-size: 12pt; color: #ab341d"><span style="color: #000000">
<p><span style="font-size: 10.5pt;background: white; color: black; font-family: 宋体">
<p align="left"><span style="font-family: 宋体">也就是说</span><span style="font-family: Times New Roman">,</span><span style="font-family: 宋体">跟据局部变量的多少</span><span style="font-family: Times New Roman">,</span><span style="font-family: 宋体">临时空间的开辟是以</span><span style="font-family: Times New Roman">0x10</span><span style="font-family: 宋体">为增长量</span><span style="font-family: Times New Roman">,</span><span style="font-family: 宋体">也许是为了内存对齐吧</span><span style="font-family: Times New Roman">,</span><span style="font-family: 宋体">而且不同的编译器实现也不一样</span><span style="font-family: Times New Roman">.</span><span style="font-family: 宋体">比如有</span><span style="font-family: Times New Roman">5</span><span style="font-family: 宋体">个</span><span style="font-family: Times New Roman">int</span><span style="font-family: 宋体">型临时变量则</span><span style="font-family: Times New Roman">sub esp,0x28;</span><span style="font-family: 宋体">有</span><span style="font-family: Times New Roman">8</span><span style="font-family: 宋体">个</span><span style="font-family: Times New Roman">int</span><span style="font-family: 宋体">型临时变量也是</span><span style="font-family: Times New Roman">sub esp,0x28;</span><span style="font-family: 宋体">到有</span><span style="font-family: Times New Roman">9</span><span style="font-family: 宋体">个</span><span style="font-family: Times New Roman">int</span><span style="font-family: 宋体">型临时变量则为</span><span style="font-family: Times New Roman">sub esp,0x38</span></p>
<p align="left">&nbsp;</p></span>
<p>&nbsp;</p>
<p>&nbsp;</p></span></span>
<p>&nbsp;</p>
<p align="left">&nbsp;<!--StartFragment -->&nbsp;<img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/ggggfjeicfh/EntryImages/20091215/4.jpg" /> </p>
<p>
<p align="left">&nbsp;</p>
<p align="left">&nbsp;</p>
<p>&nbsp;</p>
<div style="border-top-width: 1pt; padding-right: 0cm; padding-left: 0cm; border-left-width: 1pt; border-left-color: black;background: white; border-bottom-width: 1pt; border-bottom-color: black; padding-bottom: 3pt; border-top-color: black; padding-top: 0cm; border-right-width: 1pt; border-right-color: black">
<p align="left"><span style="font-size: small"><span style="background: white; color: black; font-family: 宋体">c.在函数末尾执行</span><span style="background: white; color: black; font-family: Verdana">:</span></span></p>
<p style="padding-right: 0cm; padding-left: 0cm;background: white; padding-bottom: 0cm; padding-top: 0cm; text-align: left" align="left">&nbsp;</p>
<p style="padding-right: 0cm; padding-left: 0cm;background: white; padding-bottom: 0cm; padding-top: 0cm; text-align: left" align="left"><span style="font-size: 12pt;background: white; color: #ab341d; font-family: 'Courier New'">leave</span></p>
<p style="padding-right: 0cm; padding-left: 0cm;background: white; padding-bottom: 0cm; padding-top: 0cm; text-align: left" align="left"><span style="font-size: 12pt;background: white; color: #ab341d; font-family: Verdana">ret</span></p>
<p align="left"><span style="font-size: small"><span style="background: white; color: black; font-family: Verdana">Leave</span><span style="background: white; color: black; font-family: 宋体">其实就是使</span><span style="background: white; color: black; font-family: Verdana">ESP+</span><span style="font-family: Verdana">( MIN(X)*0x10+</span><span style="font-family: Verdana">0x8</span><span style="font-family: Verdana"> )+0x4</span><span style="background: white; color: black; font-family: Verdana">,</span><span style="background: white; color: black; font-family: 宋体">更通用一些就是</span><span style="background: white; color: black; font-family: Verdana">EBP+0x4,ESP</span><span style="background: white; color: black; font-family: 宋体">指向</span><span style="background: white; color: black; font-family: Verdana">Ret EIP,</span><span style="background: white; color: black; font-family: 宋体">然后返回</span><span style="background: white; color: black; font-family: Verdana">,</span><span style="background: white; color: black; font-family: 宋体">最后由函数的调用者清理堆栈,如果是stdcall，则在函数体内作堆栈清理，再执行返回操作。<br /><br /><a href="http://blog.csdn.net/ggggfjeicfh/article/details/5003398">http://blog.csdn.net/ggggfjeicfh/article/details/5003398</a></span></span></p></div><img src ="http://www.cppblog.com/yehao/aggbug/166804.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2012-02-29 17:18 <a href="http://www.cppblog.com/yehao/articles/166804.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>程序执行时，栈指针ESP和栈顶指针EBP的变化</title><link>http://www.cppblog.com/yehao/articles/166803.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Wed, 29 Feb 2012 09:17:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/166803.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/166803.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/166803.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/166803.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/166803.html</trackback:ping><description><![CDATA[<div class="cnt" id="blog_text">
<p>进入main函数的时候，<br />1）保存ebp指针<br />2）使得ebp-&gt;esp<br />3）保持现场ebx，esi，edi<br />4）进入一般函数的时候，push参数，例如有n个参数 esp = esp - 4*n<br />5）push函数返回地址 esp = esp -4<br />6）调用函数 EIP指向函数地址，jmp 函数地址<br />7）Push ebp 保存 (故有[ebp+8]就是第一个参数的内容)<br />8）使得ebp -&gt;esp, esp = esp - 40h - 临时变量需要字节数（有字节对齐问题）<br />9）保持现场ebx，esi，edi<br />10）返回值=》eax<br />11）恢复现场，就是依次pop出edi，esi，ebx，<br />12）Esp = ebp<br />13）Pop ebp<br />14）pop 函数返回地址（即ret），EIP指向下个指令地址 esp = esp +4<br />15）Esp + n*4 (每个字有4个字节)；push是按字操作的，32位机器 </p>
<p>知道这些后，程序的溢出就比较容易做了。<br /></p></div><br /><a href="http://hi.baidu.com/goodallen/blog/item/1a2e23fc07b7e487b901a0cb.html/cmtid/494b414bcea227fc82025ca2">http://hi.baidu.com/goodallen/blog/item/1a2e23fc07b7e487b901a0cb.html/cmtid/494b414bcea227fc82025ca2</a><br /><img src ="http://www.cppblog.com/yehao/aggbug/166803.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2012-02-29 17:17 <a href="http://www.cppblog.com/yehao/articles/166803.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>通过一段汇编，加深对寄存器ESP和EBP的理解</title><link>http://www.cppblog.com/yehao/articles/166802.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Wed, 29 Feb 2012 09:17:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/166802.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/166802.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/166802.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/166802.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/166802.html</trackback:ping><description><![CDATA[<span class="Apple-style-span" style="font-size: 14px; line-height: 21px; font-family: verdana, sans-serif" minmax_bound="true">一直对寄存器ESP和EBP的概念总是有些混淆，查看定义ESP是栈顶指针，EBP是存取堆栈指针。还是不能很透彻理解。之后借于一段汇编代码，总算是对两者有个比较清晰的理解。<br minmax_bound="true" />下面是按调用约定__stdcall 调用函数test(int p1,int p2)的汇编代码<br minmax_bound="true" />;假设执行函数前堆栈指针ESP为NN<br minmax_bound="true" />push&nbsp;&nbsp; p2&nbsp;&nbsp;&nbsp; ;参数2入栈, ESP -= 4h , ESP = NN - 4h<br minmax_bound="true" />push&nbsp;&nbsp; p1&nbsp;&nbsp;&nbsp; ;参数1入栈, ESP -= 4h , ESP = NN - 8h<br minmax_bound="true" />call test&nbsp;&nbsp;&nbsp; ;压入返回地址 ESP -= 4h, ESP = NN - 0Ch&nbsp;&nbsp;<br minmax_bound="true" />;//进入函数内<br minmax_bound="true" />{<br minmax_bound="true" />push&nbsp;&nbsp; ebp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;保护先前EBP指针， EBP入栈， ESP-=4h, ESP = NN - 10h<br minmax_bound="true" />mov&nbsp;&nbsp;&nbsp; ebp, esp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;设置EBP指针指向栈顶 NN-10h<br minmax_bound="true" />mov&nbsp;&nbsp;&nbsp; eax, dword ptr&nbsp; [ebp+0ch]&nbsp; ;ebp+0ch为NN-4h,即参数2的位置<br minmax_bound="true" />mov&nbsp;&nbsp;&nbsp; ebx, dword ptr&nbsp; [ebp+08h]&nbsp; ;ebp+08h为NN-8h,即参数1的位置<br minmax_bound="true" />sub&nbsp;&nbsp;&nbsp; esp, 8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;局部变量所占空间ESP-=8, ESP = NN-18h<br minmax_bound="true" />...<br minmax_bound="true" />add&nbsp;&nbsp;&nbsp; esp, 8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;释放局部变量, ESP+=8, ESP = NN-10h<br minmax_bound="true" />pop&nbsp;&nbsp;&nbsp; ebp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;出栈,恢复EBP, ESP+=4, ESP = NN-0Ch<br minmax_bound="true" />ret&nbsp;&nbsp;&nbsp; 8&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;ret返回,弹出返回地址,ESP+=4, ESP=NN-08h, 后面加操作数8为平衡堆栈,ESP+=8,ESP=NN, 恢复进入函数前的堆栈.<br minmax_bound="true" />}<br minmax_bound="true" />看完汇编后,再看EBP和ESP的定义,哦,豁然开朗,<br minmax_bound="true" />原来ESP就是一直指向栈顶的指针,而EBP只是存取某时刻的栈顶指针,以方便对栈的操作,如获取函数参数、局部变量等。<br /><a href="http://blog.csdn.net/zsJum/archive/2011/01/05/6117043.aspx" target="_blank" minmax_bound="true">http://blog.csdn.net/zsJum/archive/2011/01/05/6117043.aspx</a> </span><img src ="http://www.cppblog.com/yehao/aggbug/166802.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2012-02-29 17:17 <a href="http://www.cppblog.com/yehao/articles/166802.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++及Windows异常处理（try，catch; __try,__finally; __try, __except） </title><link>http://www.cppblog.com/yehao/articles/165099.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Tue, 07 Feb 2012 06:32:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/165099.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/165099.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/165099.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/165099.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/165099.html</trackback:ping><description><![CDATA[<div class="article_content" id="article_content" sizset="0" sizcache="0">
<p style="font-size: 16px; font-family: Simsun">题目：</p>
<div class="csharpcode" style="font-size: 13px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)" sizset="0" sizcache="0"><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> <span class="kwrd" style="color: rgb(0,0,255)">int</span>* p = 0x00000000; <span class="rem" style="color: rgb(0,128,0)">// pointer to NULL</span></pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> puts( <span class="str" style="color: rgb(0,96,128)">"hello "</span>);</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> __try{</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> puts( <span class="str" style="color: rgb(0,96,128)">"in try "</span>);</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> __try{</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> puts( <span class="str" style="color: rgb(0,96,128)">"in try "</span>);</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> *p = 13; <span class="rem" style="color: rgb(0,128,0)">// causes an access violation exception;</span></pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> }__finally{</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> puts( <span class="str" style="color: rgb(0,96,128)">"in finally "</span>);</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> }</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> }__except(puts( <span class="str" style="color: rgb(0,96,128)">"in filter "</span>), 1){</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> puts( <span class="str" style="color: rgb(0,96,128)">"in except "</span>);</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> }</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> puts( <span class="str" style="color: rgb(0,96,128)">"world "</span>);</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> <span class="rem" style="color: rgb(0,128,0)">/*</span></pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"><span class="rem" style="color: rgb(0,128,0)"> hello</span></pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"><span class="rem" style="color: rgb(0,128,0)"> in try</span></pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"><span class="rem" style="color: rgb(0,128,0)"> in try</span></pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"><span class="rem" style="color: rgb(0,128,0)"> in filter</span></pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"><span class="rem" style="color: rgb(0,128,0)"> in finally</span></pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"><span class="rem" style="color: rgb(0,128,0)"> in except</span></pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"><span class="rem" style="color: rgb(0,128,0)"> world</span></pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"><span class="rem" style="color: rgb(0,128,0)"> */</span></pre></div>
<p style="font-size: 16px; font-family: Simsun">上面的题目，我把答案列了出来。</p>
<p style="font-size: 16px; font-family: Simsun">常用Ｃ＋＋的朋友，应该没见过__try这种形式的语句，下面我把try，catch; __try,__finally; __try, __except这三对异常处理使用标示逐一说明</p>
<p style="font-size: 16px; font-family: Simsun">&nbsp;</p>
<p style="font-size: 16px; font-family: Simsun">本文参考了如下博文：</p>
<p style="font-size: 16px; font-family: Simsun"><a title="http://www.cnblogs.com/wenziqi/archive/2010/08/26/1809074.html" href="http://www.cnblogs.com/wenziqi/archive/2010/08/26/1809074.html">http://www.cnblogs.com/wenziqi/archive/2010/08/26/1809074.html</a></p>
<p style="font-size: 16px; font-family: Simsun"><a title="http://blog.csdn.net/lvwenshuai/article/details/6163342" href="http://blog.csdn.net/lvwenshuai/article/details/6163342">http://blog.csdn.net/lvwenshuai/article/details/6163342</a></p>
<p style="font-size: 16px; font-family: Simsun"><a title="http://topic.csdn.net/t/20030527/10/1838724.html" href="http://topic.csdn.net/t/20030527/10/1838724.html">http://topic.csdn.net/t/20030527/10/1838724.html</a></p>
<p style="font-size: 16px; font-family: Simsun"><a title="http://zhidao.baidu.com/question/183400727.html" href="http://zhidao.baidu.com/question/183400727.html">http://zhidao.baidu.com/question/183400727.html</a></p>
<p style="font-size: 16px; font-family: Simsun">&nbsp;</p>
<ul style="font-size: 16px; font-family: Simsun"><li><span style="font-size: 18px; color: #0080ff"><strong>C++ 异常处理：try，catch</strong></span></li></ul><span style="font-size: 16px; font-family: Simsun"></span><pre class="csharpcode" style="font-size: 13px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)" name="code"><span class="kwrd" style="color: rgb(0,0,255)">try</span> { <span class="rem" style="color: rgb(0,128,0)">// 可能出错的语句</span> <span class="rem" style="color: rgb(0,128,0)">// 如果有错，就&#8212;&#8212;</span> <span class="kwrd" style="color: rgb(0,0,255)">throw</span> ... <span class="rem" style="color: rgb(0,128,0)">// 初始化一个异常对象（exception object）</span> } <span class="kwrd" style="color: rgb(0,0,255)">catch</span>( 类型名 [形参名] ) <span class="rem" style="color: rgb(0,128,0)">/* 异常说明符（exception specifier）*/</span> { } <span class="kwrd" style="color: rgb(0,0,255)">catch</span>( 类型名 [形参名] ) { }</pre>
<p style="font-size: 16px; font-family: Simsun">C++的异常处理很简单，就是如上的三个关键字，注意C++中throw，catch之后没有Java等语言中的finally。</p>
<p style="font-size: 16px; font-family: Simsun">Q: 为何C++不提供&#8220;finally&#8221;结构？&nbsp;<br />A: 因为C++提供了另一种机制，完全可以取代finally，而且这种机制几乎总要比finally工作得更好：就是&#8212;&#8212;&#8220;分配资源即初始化&#8221;。（见《The C++ Programming Language》14.4节）基本的想法是，用一个局部对象来封装一个资源，这样一来局部对象的析构函数就可以自动释放资源。这样，程序员就不会&#8220;忘记释放资源&#8221;了。 [译注：因为C++的对象&#8220;生命周期&#8221;机制替他记住了 :O) ] 下面是一个例子：</p>
<div class="csharpcode" style="font-size: 13px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)" sizset="24" sizcache="0"><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"><span class="kwrd" style="color: rgb(0,0,255)">class</span> File_handle {</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> FILE* p;</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> <span class="kwrd" style="color: rgb(0,0,255)">public</span>:</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> File_handle(<span class="kwrd" style="color: rgb(0,0,255)">const</span> <span class="kwrd" style="color: rgb(0,0,255)">char</span>* n, <span class="kwrd" style="color: rgb(0,0,255)">const</span> <span class="kwrd" style="color: rgb(0,0,255)">char</span>* a)</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> { p = fopen(n,a); <span class="kwrd" style="color: rgb(0,0,255)">if</span> (p==0) <span class="kwrd" style="color: rgb(0,0,255)">throw</span> Open_error(errno); }</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> File_handle(FILE* pp)</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> { p = pp; <span class="kwrd" style="color: rgb(0,0,255)">if</span> (p==0) <span class="kwrd" style="color: rgb(0,0,255)">throw</span> Open_error(errno); }</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)">&nbsp;</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> ~File_handle() { fclose(p); }</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)">&nbsp;</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> <span class="kwrd" style="color: rgb(0,0,255)">operator</span> FILE*() { <span class="kwrd" style="color: rgb(0,0,255)">return</span> p; }</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)">&nbsp;</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> <span class="rem" style="color: rgb(0,128,0)">// ...</span></pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> };</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code">&nbsp;</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> <span class="kwrd" style="color: rgb(0,0,255)">void</span> f(<span class="kwrd" style="color: rgb(0,0,255)">const</span> <span class="kwrd" style="color: rgb(0,0,255)">char</span>* fn)</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> {</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> File_handle f(fn,<span class="str" style="color: rgb(0,96,128)">"rw"</span>); <span class="rem" style="color: rgb(0,128,0)">// open fn for reading and writing</span></pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> <span class="rem" style="color: rgb(0,128,0)">// use file through f</span></pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> }</pre></div>
<p style="font-size: 16px; font-family: Simsun">在一个系统中，每一样资源都需要一个&#8220;资源局柄&#8221;对象，但我们不必为每一个资源都写一个&#8220;finally&#8221;语句。在实作的系统中，资源的获取和释放的次数远远多于资源的种类，所以&#8220;资源分配即初始化&#8221;机制产生的代码要比&#8220;finally&#8221;机制少。</p>
<p style="font-size: 16px; font-family: Simsun">&nbsp;</p>
<p style="font-size: 16px; font-family: Simsun"><strong>好了，接下来，看另外两组异常模型机制，它们是Windows系列操作系统平台上提供的SEH模型，也就是说在C++中调用的时候，其实是调用Windows的API</strong></p>
<p style="font-size: 16px; font-family: Simsun"><strong>SEH,又称结构化异常处理.是设计Windows操作系统时提出一个种处理异常的方法。</strong></p>
<p style="font-size: 16px; font-family: Simsun"><strong></strong></p>
<ul style="font-size: 16px; font-family: Simsun"><li><span style="font-size: 18px; color: #0080ff"><strong>__try, __except</strong></span></li></ul>
<p style="font-size: 16px; font-family: Simsun">这组异常处理机制和C++的很相像，只是关键字是except而不是catch</p>
<p style="font-size: 16px; font-family: Simsun"><strong>catch 和 except 的一点不同：</strong>&nbsp;catch关键字后面往往好像接受一个函数参数一样，可以是各种类型的异常数据对象；但是__except关键字则不同，它后面跟的却是一个表达式（可以是各种类型的表达式）</p>
<p style="font-size: 16px; font-family: Simsun">下面是一个例子：</p>
<div class="csharpcode" style="font-size: 13px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)" sizset="44" sizcache="0"><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"><span class="kwrd" style="color: rgb(0,0,255)">void</span> main()</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)">{</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> puts(<span class="str" style="color: rgb(0,96,128)">"hello"</span>);</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> <span class="rem" style="color: rgb(0,128,0)">// 定义受监控的代码模块</span></pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> __try</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> {</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> puts(<span class="str" style="color: rgb(0,96,128)">"in try"</span>);</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> }</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> <span class="rem" style="color: rgb(0,128,0)">//定义异常处理模块</span></pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> __except(1)</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> {</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> puts(<span class="str" style="color: rgb(0,96,128)">"in except"</span>);</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> }</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> puts(<span class="str" style="color: rgb(0,96,128)">"world"</span>);</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code">}</pre></div>
<p style="font-size: 16px; font-family: Simsun">1. 受监控的代码模块被执行（也即__try定义的模块代码）；&nbsp;<br />2. 如果上面的代码执行过程中，没有出现异常的话，那么控制流将转入到__except子句之后的代码模块中；&nbsp;<br />3. 否则，如果出现异常的话，那么控制流将进入到__except后面的表达式中，也<strong>即首先计算这个表达式的值</strong>，之后再根据这个值，来决定做出相应的处理。</p>
<p style="font-size: 16px; font-family: Simsun">EXCEPTION_CONTINUE_EXECUTION (&#8211;1) 异常被忽略，控制流将在异常出现的点之后，继续恢复运行。&nbsp;<br />EXCEPTION_CONTINUE_SEARCH (0) 异常不被识别，也即当前的这个__except模块不是这个异常错误所对应的正确的异常处理模块。系统将继续到上一层的try-except域中继续查找一个恰当的__except模块。&nbsp;<br />EXCEPTION_EXECUTE_HANDLER (1) 异常已经被识别，也即当前的这个异常错误，系统已经找到了并能够确认，这个__except模块就是正确的异常处理模块。控制流将进入到__except模块中。</p>
<p style="font-size: 16px; font-family: Simsun"><strong>小结：</strong></p>
<p style="font-size: 16px; font-family: Simsun">（1） C++异常模型用try-catch语法定义，而SEH异常模型则用try-except语法；</p>
<p style="font-size: 16px; font-family: Simsun">（2） 与C++异常模型相似，try-except也支持多层的try-except嵌套。</p>
<p style="font-size: 16px; font-family: Simsun">（3） 与C++异常模型不同的是，try-except模型中，一个try块只能是有一个except块；而C++异常模型中，一个try块可以有多个catch块。</p>
<p style="font-size: 16px; font-family: Simsun">（4） 与C++异常模型相似，try-except模型中，查找搜索异常模块的规则也是逐级向上进行的。但是稍有区别的是，C++异常模型是按照异常对象的类型来进行匹配查找的；而try-except模型则不同，它通过一个表达式的值来进行判断。如果表达式的值为1（EXCEPTION_EXECUTE_HANDLER），表示找到了异常处理模块；如果值为0（EXCEPTION_CONTINUE_SEARCH），表示继续向上一层的try-except域中继续查找其它可能匹配的异常处理模块；如果值为-1（EXCEPTION_CONTINUE_EXECUTION），表示忽略这个异常，注意这个值一般很少用，因为它很容易导致程序难以预测的结果，例如，死循环，甚至导致程序的崩溃等。</p>
<p style="font-size: 16px; font-family: Simsun">（5） __except关键字后面跟的表达式，它可以是各种类型的表达式，例如，它可以是一个函数调用，或是一个条件表达式，或是一个逗号表达式，或干脆就是一个整型常量等等。最常用的是一个函数表达式，并且通过利用GetExceptionCode()或GetExceptionInformation ()函数来获取当前的异常错误信息，便于程序员有效控制异常错误的分类处理。</p>
<p style="font-size: 16px; font-family: Simsun">（6） SEH异常处理模型中，异常被划分为两大类：系统异常和软件异常。其中软件异常通过RaiseException()函数抛出。RaiseException()函数的作用类似于C++异常模型中的throw语句。</p>
<p style="font-size: 16px; font-family: Simsun">详细的请参看：<a title="http://www.cnblogs.com/wenziqi/archive/2010/08/26/1809074.html" href="http://www.cnblogs.com/wenziqi/archive/2010/08/26/1809074.html">http://www.cnblogs.com/wenziqi/archive/2010/08/26/1809074.html</a></p>
<p style="font-size: 16px; font-family: Simsun">&nbsp;</p>
<ul style="font-size: 16px; font-family: Simsun"><li><span style="font-size: 18px; color: #0080ff"><strong>__try, __finally</strong></span></li></ul>
<p style="font-size: 16px; font-family: Simsun">try-finally语句的语法与try-except很类似，稍有不同的是，__finally后面没有一个表达式，这是因为try- finally语句的作用不是用于异常处理，所以它不需要一个表达式来判断当前异常错误的种类。另外，与try-except语句类似，try- finally也可以是多层嵌套的，并且一个函数内可以有多个try-finally语句，不管它是嵌套的，或是平行的。当然，try-finally多层嵌套也可以是跨函数的。</p>
<p style="font-size: 16px; font-family: Simsun">最关键的一点：&nbsp;<span style="font-size: 18px; color: #0080ff"><strong>&#8220;不管在何种情况下，在离开当前的作用域时，finally块区域内的代码都将会被执行到&#8221;</strong></span></p>
<div class="csharpcode" style="font-size: 13px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)" sizset="59" sizcache="0">
<div class="csharpcode" style="font-size: 13px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)" sizset="59" sizcache="0"><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"><span class="kwrd" style="color: rgb(0,0,255)">void</span> tmain()</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)">{</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> puts(<span class="str" style="color: rgb(0,96,128)">"hello"</span>);</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> __try</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> {</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> puts(<span class="str" style="color: rgb(0,96,128)">"__try块中"</span>);</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code">&nbsp;</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> <span class="rem" style="color: rgb(0,128,0)">// 注意，下面return语句直接让函数返回了</span></pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> <span class="kwrd" style="color: rgb(0,0,255)">return</span>;</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> }</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> __finally</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> {</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code"> puts(<span class="str" style="color: rgb(0,96,128)">"__finally块中"</span>);</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> }</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code">&nbsp;</pre><pre style="font-size: 13px; margin: 0em; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(255,255,255)"> puts(<span class="str" style="color: rgb(0,96,128)">"world"</span>);</pre><pre class="alt" style="font-size: 13px; margin: 0em; width: 1303px; color: black; font-family: consolas,'Courier New',courier,monospace; background-color: rgb(244,244,244)" name="code">｝</pre></div></div>
<p style="font-size: 16px; font-family: Simsun">上面的程序运行结果如下：&nbsp;<br />hello&nbsp;<br />__try块中&nbsp;<br />__finally块中&nbsp;<br />Press any key to continue</p>
<p style="font-size: 16px; font-family: Simsun">&nbsp;</p>
<p style="font-size: 16px; font-family: Simsun"></p>
<p style="font-size: 16px; font-family: Simsun"><strong>小结：</strong></p>
<p style="font-size: 16px; font-family: Simsun">__finally块被执行的流程时，无外乎三种情况。</p>
<p style="font-size: 16px; font-family: Simsun"><strong>第一种</strong>就是顺序执行到__finally块区域内的代码，这种情况很简单，容易理解；</p>
<p style="font-size: 16px; font-family: Simsun"><strong>第二种</strong>就是goto语句或return语句引发的程序控制流离开当前__try块作用域时，系统自动完成对__finally块代码的调用；</p>
<p style="font-size: 16px; font-family: Simsun"><strong>第三种</strong>就是由于在__try块中出现异常时，导致程序控制流离开当前__try块作用域，这种情况下也是由系统自动完成对__finally块的调用。</p>
<p style="font-size: 16px; font-family: Simsun">无论是第 2种，还是第3种情况，毫无疑问，它们都会引起很大的系统开销，编译器在编译此类程序代码时，它会为这两种情况准备很多的额外代码。</p>
<p style="font-size: 16px; font-family: Simsun">一般第2种情况，被称为&#8220;局部展开（LocalUnwinding）&#8221;；第3种情况，被称为&#8220;全局展开（GlobalUnwinding）&#8221;。在后面阐述SEH实现的时候会详细分析到这一点。</p>
<p style="font-size: 16px; font-family: Simsun">第3种情况，也即由于出现异常而导致的&#8220;全局展开&#8221;，对于程序员而言，这也许是无法避免的，因为你在利用异常处理机制提高程序可靠健壮性的同时，不可避免的会引起性能上其它的一些开销。呵呵！这世界其实也算瞒公平的，有得必有失。</p>
<p style="font-size: 16px; font-family: Simsun"><br /></p>
<p style="font-size: 16px; font-family: Simsun">到此，本文开头的那段代码的结果就没有任何悬念了，O(&#8745;_&#8745;)O哈哈~，每天进步一点点~~~~~~~~</p>
<p style="font-size: 16px; font-family: Simsun">转自：<span style="font-size: 16px; line-height: 28px; font-family: Simsun"><a href="http://shijuanfeng.blogbus.com/logs/178616871.html">http://shijuanfeng.blogbus.com/logs/178616871.html</a></span></p></div><img src ="http://www.cppblog.com/yehao/aggbug/165099.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2012-02-07 14:32 <a href="http://www.cppblog.com/yehao/articles/165099.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个跨平台的 C++ 内存泄漏检测器</title><link>http://www.cppblog.com/yehao/articles/158632.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Tue, 18 Oct 2011 13:20:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/158632.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/158632.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/158632.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/158632.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/158632.html</trackback:ping><description><![CDATA[<p sizcache="23" sizset="78"><a name="1"><span class="atitle"></p>
<p><strong>简介：</strong>&nbsp;内存泄漏对于C/C++程序员来说也可以算作是个永恒的话题了吧。在Windows下，MFC的一个很有用的功能就是能在程序运行结束时报告是否发生了内存泄漏。在Linux下，相对来说就没有那么容易使用的解决方案了：像mpatrol之类的现有工具，易用性、附加开销和性能都不是很理想。本文实现一个极易于使用、跨平台的C++内存泄漏检测器。并对相关的技术问题作一下探讨。</p>
<p sizcache="23" sizset="78">基本使用</span></a></p>
<p>对于下面这样的一个简单程序test.cpp：</p>
<table border="0" cellspacing="0" cellpadding="0" width="600">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">int main()
{
	int* p1 = new int;
	char* p2 = new char[10];
	return 0;
}
</pre></td></tr></tbody></table><br />
<p>我们的基本需求当然是对于该程序报告存在两处内存泄漏。要做到这点的话，非常简单，只要把debug_new.cpp也编译、链接进去就可以了。在Linux下，我们使用：</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">g++ test.cpp debug_new.cpp -o test
</pre></td></tr></tbody></table><br />
<p>输出结果如下所示：</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">Leaked object at 0x805e438 (size 10, &lt;Unknown&gt;:0)
Leaked object at 0x805e410 (size 4, &lt;Unknown&gt;:0)
</pre></td></tr></tbody></table><br />
<p>如果我们需要更清晰的报告，也很简单，在test.cpp开头加一行</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">#include "debug_new.h"
</pre></td></tr></tbody></table><br />
<p>即可。添加该行后的输出如下：</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">Leaked object at 0x805e438 (size 10, test.cpp:5)
Leaked object at 0x805e410 (size 4, test.cpp:4)
</pre></td></tr></tbody></table><br />
<p>非常简单！</p>
<div class="ibm-alternate-rule">
<hr />
</div>
<p class="ibm-ind-link ibm-back-to-top" sizcache="23" sizset="79"><a class="ibm-anchor-up-link" href="http://www.ibm.com/developerworks/cn/linux/l-mleak2/index.html#ibm-pcon">回页首</a></p>
<p sizcache="23" sizset="80"><a name="2"><span class="atitle">背景知识</span></a></p>
<p>在new/delete操作中，C++为用户产生了对operator new和operator delete的调用。这是用户不能改变的。operator new和operator delete的原型如下所示：</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">void *operator new(size_t) throw(std::bad_alloc);
void *operator new[](size_t) throw(std::bad_alloc);
void operator delete(void*) throw();
void operator delete[](void*) throw();
</pre></td></tr></tbody></table><br />
<p>对于"new int"，编译器会产生一个调用"operator new(sizeof(int))"，而对于"new char[10]"，编译器会产生"operator new[](sizeof(char) * 10)"（如果new后面跟的是一个类名的话，当然还要调用该类的构造函数）。类似地，对于"delete ptr"和"delete[] ptr"，编译器会产生"operator delete(ptr)"调用和"operator delete[](ptr)"调用（如果ptr的类型是指向对象的指针的话，那在operator delete之前还要调用对象的析构函数）。当用户没有提供这些操作符时，编译系统自动提供其定义；而当用户自己提供了这些操作符时，就覆盖了编译系统提供的版本，从而可获得对动态内存分配操作的精确跟踪和控制。</p>
<p>同时，我们还可以使用placement new操作符来调整operator new的行为。所谓placement new，是指带有附加参数的new操作符，比如，当我们提供了一个原型为</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">void* operator new(size_t size, const char* file, int line);
</pre></td></tr></tbody></table><br />
<p>的操作符时，我们就可以使用"new("hello", 123) int"来产生一个调用"operator new(sizeof(int), "hello", 123)"。这可以是相当灵活的。又如，C++标准要求编译器提供的一个placement new操作符是</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">void* operator new(size_t size, const std::nothrow_t&amp;);
</pre></td></tr></tbody></table><br />
<p>其中，nothrow_t通常是一个空结构（定义为"struct nothrow_t {};"），其唯一目的是提供编译器一个可根据重载规则识别具体调用的类型。用户一般简单地使用"new(std::nothrow) 类型"（nothrow是一个nothrow_t类型的常量）来调用这个placement new操作符。它与标准new的区别是，new在分配内存失败时会抛出异常，而"new(std::nothrow)"在分配内存失败时会返回一个空指针。</p>
<p>要注意的是，没有对应的"delete(std::nothrow) ptr"的语法；不过后文会提到另一个相关问题。</p>
<p>要进一步了解以上关于C++语言特性的信息，请参阅[Stroustrup1997]，特别是6.2.6、10.4.11、15.6、19.4.5和B.3.4节。这些C++语言特性是理解本实现的关键。</p>
<div class="ibm-alternate-rule">
<hr />
</div>
<p class="ibm-ind-link ibm-back-to-top" sizcache="23" sizset="81"><a class="ibm-anchor-up-link" href="http://www.ibm.com/developerworks/cn/linux/l-mleak2/index.html#ibm-pcon">回页首</a></p>
<p sizcache="23" sizset="82"><a name="3"><span class="atitle">检测原理</span></a></p>
<p>和其它一些内存泄漏检测的方式类似，debug_new中提供了operator new重载，并使用了宏在用户程序中进行替换。debug_new.h中的相关部分如下：</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">void* operator new(size_t size, const char* file, int line);
void* operator new[](size_t size, const char* file, int line);
#define new DEBUG_NEW
#define DEBUG_NEW new(__FILE__, __LINE__)
</pre></td></tr></tbody></table><br />
<p>拿上面加入debug_new.h包含后的test.cpp来说，"new char[10]"在预处理后会变成"new("test.cpp", 4) char[10]"，编译器会据此产生一个"operator new[](sizeof(char) * 10, "test.cpp", 4)"调用。通过在debug_new.cpp中自定义"operator new(size_t, const char*, int)"和"operator delete(void*)"（以及"operator new[]&#8230;"和"operator delete[]&#8230;"；为避免行文累赘，以下不特别指出，说到operator new和operator delete均同时包含数组版本），我可以跟踪所有的内存分配调用，并在指定的检查点上对不匹配的new和delete操作进行报警。实现可以相当简单，用map记录所有分配的内存指针就可以了：new时往map里加一个指针及其对应的信息，delete时删除指针及对应的信息；delete时如果map里不存在该指针为错误删除；程序退出时如果map里还存在未删除的指针则说明有内存泄漏。</p>
<p>不过，如果不包含debug_new.h，这种方法就起不了作用了。不仅如此，部分文件包含debug_new.h，部分不包含debug_new.h都是不可行的。因为虽然我们使用了两种不同的operator new --"operator new(size_t, const char*, int)"和"operator new(size_t)"-- 但可用的"operator delete"还是只有一种！使用我们自定义的"operator delete"，当我们删除由"operator new(size_t)"分配的指针时，程序将认为被删除的是一个非法指针！我们处于一个两难境地：要么对这种情况产生误报，要么对重复删除同一指针两次不予报警：都不是可接受的良好行为。</p>
<p>看来，自定义全局"operator new(size_t)"也是不可避免的了。在debug_new中，我是这样做的：</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">void* operator new(size_t size)
{
	return operator new(size, "&lt;Unknown&gt;", 0);
}
</pre></td></tr></tbody></table><br />
<p>但前面描述的方式去实现内存泄漏检测器，在某些C++的实现中（如GCC 2.95.3中带的SGI STL）工作正常，但在另外一些实现中会莫名其妙地崩溃。原因也不复杂，SGI STL使用了内存池，一次分配一大片内存，因而使利用map成为可能；但在其他的实现可能没这样做，在map中添加数据会调用operator new，而operator new会在map中添加数据，从而构成一个死循环，导致内存溢出，应用程序立即崩溃。因此，我们不得不停止使用方便的STL模板，而使用手工构建的数据结构：</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">struct new_ptr_list_t
{
	new_ptr_list_t*		next;
	const char*			file;
	int					line;
	size_t				size;
};
</pre></td></tr></tbody></table><br />
<p>我最初的实现方法就是每次在使用new分配内存时，调用malloc多分配 sizeof(new_ptr_list_t) 个字节，把分配的内存全部串成一个一个链表（利用next字段），把文件名、行号、对象大小信息分别存入file、line和size字段中，然后返回(malloc返回的指针 + sizeof(new_ptr_list_t))。在delete时，则在链表中搜索，如果找到的话（(char*)链表指针 + sizeof(new_ptr_list_t) == 待释放的指针），则调整链表、释放内存，找不到的话报告删除非法指针并abort。</p>
<p>至于自动检测内存泄漏，我的做法是生成一个静态全局对象（根据C++的对象生命期，在程序初始化时会调用该对象的构造函数，在其退出时会调用该对象的析构函数），在其析构函数中调用检测内存泄漏的函数。用户手工调用内存泄漏检测函数当然也是可以的。</p>
<p>基本实现大体就是如此。</p>
<div class="ibm-alternate-rule">
<hr />
</div>
<p class="ibm-ind-link ibm-back-to-top" sizcache="23" sizset="83"><a class="ibm-anchor-up-link" href="http://www.ibm.com/developerworks/cn/linux/l-mleak2/index.html#ibm-pcon">回页首</a></p>
<p sizcache="23" sizset="84"><a name="4"><span class="atitle">可用性改进</span></a></p>
<p>上述方案最初工作得相当好，直到我开始创建大量的对象为止。由于每次delete时需要在链表中进行搜索，平均搜索次数为(链表长度/2)，程序很快就慢得像乌龟爬。虽说只是用于调试，速度太慢也是不能接受的。因此，我做了一个小更改，把指向链表头部的new_ptr_list改成了一个数组，一个对象指针放在哪一个链表中则由它的哈希值决定。--用户可以更改宏DEBUG_NEW_HASH和DEBUG_NEW_HASHTABLESIZE的定义来调整debug_new的行为。他们的当前值是我测试下来比较满意的定义。</p>
<p>使用中我们发现，在某些特殊情况下（请直接参看debug_new.cpp中关于DEBUG_NEW_FILENAME_LEN部分的注释），文件名指针会失效。因此，目前的debug_new的缺省行为会复制文件名的头20个字符，而不只是存储文件名的指针。另外，请注意原先new_ptr_list_t的长度为16字节，现在是32字节，都能保证在通常情况下内存对齐。</p>
<p>此外，为了允许程序能和 new(std::nothrow) 一起工作，我也重载了operator new(size_t, const std::nothrow_t&amp;) throw()；不然的话，debug_new会认为对应于 new(nothrow) 的delete调用删除的是一个非法指针。由于debug_new不抛出异常（内存不足时程序直接报警退出），所以这一重载的操作只不过是调用 operator new(size_t) 而已。这就不用多说了。</p>
<p>前面已经提到，要得到精确的内存泄漏检测报告，可以在文件开头包含"debug_new.h"。我的惯常做法可以用作参考：</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">#ifdef _DEBUG
#include "debug_new.h"
#endif
</pre></td></tr></tbody></table><br />
<p>包含的位置应当尽可能早，除非跟系统的头文件（典型情况是STL的头文件）发生了冲突。在某些情况下，可能会不希望debug_new重定义new，这时可以在包含debug_new.h之前定义DEBUG_NEW_NO_NEW_REDEFINITION，这样的话，在用户应用程序中应使用debug_new来代替new（顺便提一句，没有定义DEBUG_NEW_NO_NEW_REDEFINITION时也可以使用debug_new代替new）。在源文件中也许就该这样写：</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">#ifdef _DEBUG
#define DEBUG_NEW_NO_NEW_REDEFINITION
#include "debug_new.h"
#else
#define debug_new new
#endif
</pre></td></tr></tbody></table><br />
<p>并在需要追踪内存分配的时候全部使用debug_new（考虑使用全局替换）。</p>
<p>用户可以选择定义DEBUG_NEW_EMULATE_MALLOC，这样debug_new.h会使用debug_new和delete来模拟malloc和free操作，使得用户程序中的malloc和free操作也可以被跟踪。在使用某些编译器的时候（如Digital Mars C++ Compiler 8.29和Borland C++ Compiler 5.5.1），用户必须定义NO_PLACEMENT_DELETE，否则编译无法通过。用户还可以使用两个全局布尔量来调整debug_new的行为：new_verbose_flag，缺省为false，定义为true时能在每次new/delete时向标准错误输出显示跟踪信息；new_autocheck_flag，缺省为true，即在程序退出时自动调用check_leaks检查内存泄漏，改为false的话用户必须手工调用check_leaks来检查内存泄漏。</p>
<p>需要注意的一点是，由于自动调用check_leaks是在debug_new.cpp中的静态对象析构时，因此不能保证用户的全局对象的析构操作发生在check_leaks调用之前。对于Windows上的MSVC，我使用了"#pragma init_seg(lib)"来调整对象分配释放的顺序，但很遗憾，我不知道在其他的一些编译器中（特别是，我没能成功地在GCC中解决这一问题）怎么做到这一点。为了减少误报警，我采取的方式是在自动调用了check_leaks之后设new_verbose_flag为true；这样，就算误报告了内存泄漏，随后的delete操作还是会被打印显示出来。只要泄漏报告和delete报告的内容一致，我们仍可以判断出没有发生内存泄漏。</p>
<p>Debug_new也能检测对同一指针重复调用delete（或delete无效指针）的错误。程序将显示错误的指针值，并强制调用abort退出。</p>
<p>还有一个问题是异常处理。这值得用专门的一节来进行说明。</p>
<div class="ibm-alternate-rule">
<hr />
</div>
<p class="ibm-ind-link ibm-back-to-top" sizcache="23" sizset="85"><a class="ibm-anchor-up-link" href="http://www.ibm.com/developerworks/cn/linux/l-mleak2/index.html#ibm-pcon">回页首</a></p>
<p sizcache="23" sizset="86"><a name="5"><span class="atitle">构造函数中的异常</span></a></p>
<p>我们看一下以下的简单程序示例：</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">#include &lt;stdexcept&gt;
#include &lt;stdio.h&gt;
void* operator new(size_t size, int line)
{
	printf("Allocate %u bytes on line %d\\n", size, line);
	return operator new(size);
}
class Obj {
public:
	Obj(int n);
private:
	int _n;
};
Obj::Obj(int n) : _n(n)
{
	if (n == 0) {
		throw std::runtime_error("0 not allowed");
	}
}
int main()
{
	try {
		Obj* p = new(__LINE__) Obj(0);
		delete p;
	} catch (const std::runtime_error&amp; e) {
		printf("Exception: %s\\n", e.what());
	}
}
</pre></td></tr></tbody></table><br />
<p>看出代码中有什么问题了吗？实际上，如果我们用MSVC编译的话，编译器的警告信息已经告诉我们发生了什么：</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">test.cpp(27) : warning C4291: 'void *__cdecl operator new(unsigned int,int)' : 
no matching operator delete found; memory will not be freed if initialization throws
 an exception
</pre></td></tr></tbody></table><br />
<p>好，把debug_new.cpp链接进去。运行结果如下：</p>
<table border="0" cellspacing="0" cellpadding="0" width="100%">
<tbody>
<tr>
<td class="code-outline"><pre class="displaycode">Allocate 4 bytes on line 27 Exception: 0 not allowed Leaked object at 
 00342BE8 (size 4, &lt;Unknown&gt;:0) 	</pre></td></tr></tbody></table><br />
<p>啊哦，内存泄漏了不是！</p>
<p sizcache="23" sizset="87">当然，这种情况并非很常见。可是，随着对象越来越复杂，谁能够保证一个对象的子对象的构造函数或者一个对象在构造函数中调用的所有函数都不会抛出异常？并且，解决该问题的方法并不复杂，只是需要编译器对 C++ 标准有较好支持，允许用户定义 placement delete 算符（[C++1998]，5.3.4节；网上可以找到1996年的标准草案，比如下面的网址 <a href="http://www.comnets.rwth-aachen.de/doc/c++std/expr.html#expr.new">http://www.comnets.rwth-aachen.de/doc/c++std/expr.html#expr.new</a>）。在我测试的编译器中，GCC（2.95.3或更高版本，Linux/Windows）和MSVC（6.0或更高版本）没有问题，而Borland C++ Compiler 5.5.1和Digital Mars C++ Compiler（到v8.38为止的所有版本）则不支持该项特性。在上面的例子中，如果编译器支持的话，我们就需要声明并实现 operator delete(void*, int) 来回收new分配的内存。编译器不支持的话，需要使用宏让编译器忽略相关的声明和实现。如果要让debug_new在Borland C++ Compiler 5.5.1或Digital Mars C++ Compiler下编译的话，用户必须定义宏NO_PLACEMENT_DELETE；当然，用户得自己注意小心构造函数中抛出异常这个问题了。 </p>
<div class="ibm-alternate-rule">
<hr />
</div>
<p class="ibm-ind-link ibm-back-to-top" sizcache="23" sizset="88"><a class="ibm-anchor-up-link" href="http://www.ibm.com/developerworks/cn/linux/l-mleak2/index.html#ibm-pcon">回页首</a></p>
<p sizcache="23" sizset="89"><a name="6"><span class="atitle">方案比较</span></a></p>
<p>IBM developerWorks上刊载了洪琨先生设计实现的一个Linux上的内存泄漏检测方法（[洪琨2003]）。我的方案与其相比，主要区别如下：</p>
<p>优点： </p>
<ul><li>跨平台：只使用标准函数，并且在GCC 2.95.3/3.2（Linux/Windows）、MSVC 6、Digital Mars C++ 8.29、Borland C++ 5.5.1等多个编译器下调试通过。（虽然Linux是我的主要开发平台，但我发现，有时候能在Windows下编译运行代码还是非常方便的。）</li><li>易用性：由于重载了operator new(size_t)--洪琨先生只重载了operator new(size_t, const char*, int)--即使不包含我的头文件也能检测内存泄漏；程序退出时能自动检测内存泄漏；可以检测用户程序（不包括系统/库文件）中malloc/free产生的内存泄漏。</li><li>灵活性：有多个灵活的可配置项，可使用宏定义进行编译时选择。</li><li>可重入性：不使用全局变量，没有嵌套delete问题。</li><li>异常安全性：在编译器支持的情况下，能够处理构造函数中抛出的异常而不发生内存泄漏。 </li></ul>
<p>缺点： </p>
<ul><li>单线程模型：跨平台的多线程实现较为麻烦，根据项目的实际需要，也为了代码清晰简单起见，我的方案不是线程安全的；换句话说，如果多个线程中同时进行new或delete操作的话，后果未定义。</li><li>未实现运行中内存泄漏检测报告机制：没有遇到这个需求J；不过，如果要手工调用check_leaks函数实现的话也不困难，只是跨平台性就有点问题了。</li><li>不能检测带 [] 算符和不带 [] 算符混用的不匹配：主要也是需求问题（如果要修改实现的话并不困难）。</li><li>不能在错误的delete调用时显示文件名和行号：应该不是大问题；由于我重载了operator new(size_t)，可以保证delete出错时程序必然有问题，因而我不只是显示警告信息，而且会强制程序abort，可以通过跟踪程序、检查abort时程序的调用栈知道问题出在哪儿。 </li></ul>
<p>另外，现在已存在不少商业和Open Source的内存泄漏检测器，本文不打算一一再做比较。Debug_new与它们相比，功能上总的来说仍较弱，但是，其良好的易用性和跨平台性、低廉的附加开销还是具有很大优势的。</p>
<div class="ibm-alternate-rule">
<hr />
</div>
<p class="ibm-ind-link ibm-back-to-top" sizcache="23" sizset="90"><a class="ibm-anchor-up-link" href="http://www.ibm.com/developerworks/cn/linux/l-mleak2/index.html#ibm-pcon">回页首</a></p>
<p sizcache="23" sizset="91"><a name="7"><span class="atitle">总结和讨论</span></a></p>
<p>以上段落基本上已经说明了debug_new的主要特点。下面做一个小小的总结。</p>
<p>重载的算符： </p>
<ul><li>operator new(size_t, const char*, int)</li><li>operator new[](size_t, const char*, int)</li><li>operator new(size_t)</li><li>operator new[](size_t)</li><li>operator new(size_t, const std::nothrow_t&amp;)</li><li>operator new[](size_t, const std::nothrow_t&amp;)</li><li>operator delete(void*)</li><li>operator delete[](void*)</li><li>operator delete(void*, const char*, int)</li><li>operator delete[](void*, const char*, int)</li><li>operator delete(void*, const std::nothrow_t&amp;)</li><li>operator delete[](void*, const std::nothrow_t&amp;) </li></ul>
<p>提供的函数： </p>
<ul><li>check_leaks() <br />检查是否发生内存泄漏 </li></ul>
<p>提供的全局变量 </p>
<ul><li>new_verbose_flag <br />是否在new和delete时"罗嗦"地显示信息</li><li>new_autocheck_flag <br />是否在程序退出是自动检测一次内存泄漏 </li></ul>
<p>可重定义的宏： </p>
<ul><li>NO_PLACEMENT_DELETE <br />假设编译器不支持placement delete（全局有效）</li><li>DEBUG_NEW_NO_NEW_REDEFINITION <br />不重定义new，假设用户会自己使用debug_new（包含debug_new.h时有效）</li><li>DEBUG_NEW_EMULATE_MALLOC <br />重定义malloc/free，使用new/delete进行模拟（包含debug_new.h时有效）</li><li>DEBUG_NEW_HASH <br />改变内存块链表哈希值的算法（编译debug_new.cpp时有效）</li><li>DEBUG_NEW_HASHTABLE_SIZE <br />改变内存块链表哈希桶的大小（编译debug_new.cpp时有效）</li><li>DEBUG_NEW_FILENAME_LEN <br />如果在分配内存时复制文件名的话，保留的文件名长度；为0时则自动定义DEBUG_NEW_NO_FILENAME_COPY（编译debug_new.cpp时有效；参见文件中的注释）</li><li>DEBUG_NEW_NO_FILENAME_COPY <br />分配内存时不进行文件名复制，而只是保存其指针；效率较高（编译debug_new.cpp时有效；参见文件中的注释） </li></ul>
<p>我本人认为，debug_new目前的一个主要缺陷是不支持多线程。对于某一特定平台，要加入多线程支持并不困难，难就难在通用上（当然，条件编译是一个办法，虽然不够优雅）。等到C++标准中包含线程模型时，这个问题也许能比较完美地解决吧。另一个办法是使用像boost这样的程序库中的线程封装类，不过，这又会增加对其它库的依赖性--毕竟boost并不是C++标准的一部分。如果项目本身并不用boost，单为了这一个目的使用另外一个程序库似乎并不值得。因此，我自己暂时就不做这进一步的改进了。</p>
<p>另外一个可能的修改是保留标准operator new的异常行为，使其在内存不足的情况下抛出异常（普通情况）或是返回NULL（nothrow情况），而不是像现在一样终止程序运行（参见debug_new.cpp的源代码）。这一做法的难度主要在于后者：我没想出什么方法，可以保留 new(nothrow) 的语法，同时能够报告文件名和行号，并且还能够使用普通的new。不过，如果不使用标准语法，一律使用debug_new和debug_new_nothrow的话，那还是非常容易实现的。</p>
<p>如果大家有改进意见或其它想法的话，欢迎来信讨论。</p>
<p sizcache="23" sizset="92">debug_new 的源代码目前可以在 <a href="http://www.ibm.com/developerworks/cn/linux/l-mleak2/dbg_new.zip">dbg_new.zip</a>处下载。 </p>
<p sizcache="23" sizset="93">在这篇文章的写完之后，我终于还是实现了一个线程安全的版本。该版本使用了一个轻量级的跨平台互斥体类fast_mutex（目前支持Win32和POSIX线程，在使用GCC（Linux/MinGW）、MSVC时能通过命令行参数自动检测线程类型）。有兴趣的话可在 <a href="http://mywebpage.netscape.com/yongweiwu/dbg_new.tgz">http://mywebpage.netscape.com/yongweiwu/dbg_new.tgz</a>下载。 </p><!-- CMA ID: 21160 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file:  dw-article-6.0-beta.xsl --><br />
<p sizcache="23" sizset="94"><a name="resources"><span class="atitle">参考资料 </span></a></p>
<p>[C++1998] ISO/IEC 14882. Programming Languages-C++, 1st Edition. International Standardization Organization, International Electrotechnical Commission, American National Standards Institute, and Information Technology Industry Council, 1998</p>
<p>[Stroustrup1997] Bjarne Stroustrup. The C++ Programming Language, 3rd Edition. Addison-Wesley, 1997</p>
<p sizcache="23" sizset="95">[洪琨2003] 洪琨。 <a href="http://www.ibm.com/developerworks/cn/linux/l-mleak/index.html">《如何在 linux 下检测内存泄漏》</a>，IBM developerWorks 中国网站。 </p><img src ="http://www.cppblog.com/yehao/aggbug/158632.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-10-18 21:20 <a href="http://www.cppblog.com/yehao/articles/158632.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>浅谈C/C++内存泄漏及其检测工具</title><link>http://www.cppblog.com/yehao/articles/157969.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Mon, 10 Oct 2011 07:09:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/157969.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/157969.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/157969.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/157969.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/157969.html</trackback:ping><description><![CDATA[对于一个c/c++程序员来说，内存泄漏是一个常见的也是令人头疼的问题。已经有许多技术被研究出来以应对这个问题，比如Smart Pointer，Garbage Collection等。Smart Pointer技术比较成熟，STL中已经包含支持Smart Pointer的class，但是它的使用似乎并不广泛，而且它也不能解决所有的问题；Garbage Collection技术在Java中已经比较成熟，但是在c/c++领域的发展并不顺畅，虽然很早就有人思考在C++中也加入GC的支持。现实世界就是这样的，作为一个c/c++程序员，内存泄漏是你心中永远的痛。不过好在现在有许多工具能够帮助我们验证内存泄漏的存在，找出发生问题的代码。<br /><br />　　<strong>内存泄漏的定义</strong> <br /><br />　　一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的，大小任意的（内存块的大小可以在程序运行期决定），使用完后必须显示释放的内存。应用程序一般使用malloc，realloc，new等函数从堆中分配到一块内存，使用完后，程序必须负责相应的调用free或delete释放该内存块，否则，这块内存就不能被再次使用，我们就说这块内存泄漏了。以下这段小程序演示了堆内存发生泄漏的情形：<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>void MyFunction(int nSize)<br />{<br />　char* p= new char[nSize];<br />　if( !GetStringFrom( p, nSize ) ){<br />　　MessageBox(&#8220;Error&#8221;);<br />　　return;<br />　}<br />　&#8230;//using the string pointed by p;<br />　delete p;<br />}</td></tr></tbody></table><br />　　例一<br /><br />　　当函数GetStringFrom()返回零的时候，指针p指向的内存就不会被释放。这是一种常见的发生内存泄漏的情形。程序在入口处分配内存，在出口处释放内存，但是c函数可以在任何地方退出，所以一旦有某个出口处没有释放应该释放的内存，就会发生内存泄漏。<br /><br />　　广义的说，内存泄漏不仅仅包含堆内存的泄漏，还包含系统资源的泄漏(resource leak)，比如核心态HANDLE，GDI Object，SOCKET， Interface等，从根本上说这些由操作系统分配的对象也消耗内存，如果这些对象发生泄漏最终也会导致内存的泄漏。而且，某些对象消耗的是核心态内存，这些对象严重泄漏时会导致整个操作系统不稳定。所以相比之下，系统资源的泄漏比堆内存的泄漏更为严重。<br /><br />　　GDI Object的泄漏是一种常见的资源泄漏：<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>void CMyView::OnPaint( CDC* pDC )<br />{<br />　CBitmap bmp;<br />　CBitmap* pOldBmp;<br />　bmp.LoadBitmap(IDB_MYBMP);<br />　pOldBmp = pDC-&gt;SelectObject( &amp;bmp );<br />　&#8230;<br />　if( Something() ){<br />　　return;<br />　}<br />　pDC-&gt;SelectObject( pOldBmp );<br />　return;<br />}</td></tr></tbody></table><br />　　例二<br /><br />　　当函数Something()返回非零的时候，程序在退出前没有把pOldBmp选回pDC中，这会导致pOldBmp指向的HBITMAP对象发生泄漏。这个程序如果长时间的运行，可能会导致整个系统花屏。这种问题在Win9x下比较容易暴露出来，因为Win9x的GDI堆比Win2k或NT的要小很多。<br /><br />　　内存泄漏的发生方式：<br /><br />　　以发生的方式来分类，内存泄漏可以分为4类：<br /><br />　　1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到，每次被执行的时候都会导致一块内存泄漏。比如例二，如果Something()函数一直返回True，那么pOldBmp指向的HBITMAP对象总是发生泄漏。<br /><br />　　2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。比如例二，如果Something()函数只有在特定环境下才返回True，那么pOldBmp指向的HBITMAP对象并不总是发生泄漏。常发性和偶发性是相对的。对于特定的环境，偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。<br /><br />　　3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次，或者由于算法上的缺陷，导致总会有一块仅且一块内存发生泄漏。比如，在类的构造函数中分配内存，在析构函数中却没有释放该内存，但是因为这个类是一个Singleton，所以内存泄漏只会发生一次。另一个例子：<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>char* g_lpszFileName = NULL;<br /><br />void SetFileName( const char* lpcszFileName )<br />{<br />　if( g_lpszFileName ){<br />　　free( g_lpszFileName );<br />　}<br />　g_lpszFileName = strdup( lpcszFileName );<br />}</td></tr></tbody></table><br />　　例三<br /><br />　　如果程序在结束的时候没有释放g_lpszFileName指向的字符串，那么，即使多次调用SetFileName()，总会有一块内存，而且仅有一块内存发生泄漏。<br /><br />　　4. 隐式内存泄漏。程序在运行过程中不停的分配内存，但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏，因为最终程序释放了所有申请的内存。但是对于一个服务器程序，需要运行几天，几周甚至几个月，不及时释放内存也可能导致最终耗尽系统的所有内存。所以，我们称这类内存泄漏为隐式内存泄漏。举一个例子： <br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>class Connection<br />{<br />　public:<br />　　Connection( SOCKET s);<br />　　~Connection();<br />　　&#8230;<br />　private:<br />　　SOCKET _socket;<br />　　&#8230;<br />};<br /><br />class ConnectionManager<br />{<br />　public:<br />　　ConnectionManager(){}<br />　　~ConnectionManager(){<br />　　　list::iterator it;<br />　　　for( it = _connlist.begin(); it != _connlist.end(); ++it ){<br />　　　　delete （*it）;<br />　　　}<br />　　　_connlist.clear();<br />　　}<br />　　void OnClientConnected( SOCKET s ){<br />　　　Connection* p = new Connection(s);<br />　　　_connlist.push_back(p);<br />　　}<br />　　void OnClientDisconnected( Connection* pconn ){<br />　　　_connlist.remove( pconn );<br />　　　delete pconn;<br />　　}<br />　private:<br />　　list _connlist;<br />};</td></tr></tbody></table><br />　　例四<br /><br />　　假设在Client从Server端断开后，Server并没有呼叫OnClientDisconnected()函数，那么代表那次连接的Connection对象就不会被及时的删除（在Server程序退出的时候，所有Connection对象会在ConnectionManager的析构函数里被删除）。当不断的有连接建立、断开时隐式内存泄漏就发生了。<br /><br />　　从用户使用程序的角度来看，内存泄漏本身不会产生什么危害，作为一般的用户，根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积，这会最终消耗尽系统所有的内存。从这个角度来说，一次性内存泄漏并没有什么危害，因为它不会堆积，而隐式内存泄漏危害性则非常大，因为较之于常发性和偶发性内存泄漏它更难被检测到。<br /><strong>检测内存泄漏<br /><br /></strong>　　检测内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住这两个函数，我们就能跟踪每一块内存的生命周期，比如，每当成功的分配一块内存后，就把它的指针加入一个全局的list中；每当释放一块内存，再把它的指针从list中删除。这样，当程序结束的时候，list中剩余的指针就是指向那些没有被释放的内存。这里只是简单的描述了检测内存泄漏的基本原理，详细的算法可以参见Steve Maguire的&lt;&lt;Writing Solid Code&gt;&gt;。<br /><br />　　如果要检测堆内存的泄漏，那么需要截获住malloc/realloc/free和new/delete就可以了（其实new/delete最终也是用malloc/free的，所以只要截获前面一组即可）。对于其他的泄漏，可以采用类似的方法，截获住相应的分配和释放函数。比如，要检测BSTR的泄漏，就需要截获SysAllocString/SysFreeString；要检测HMENU的泄漏，就需要截获CreateMenu/ DestroyMenu。（有的资源的分配函数有多个，释放函数只有一个，比如，SysAllocStringLen也可以用来分配BSTR，这时就需要截获多个分配函数）<br /><br />　　在Windows平台下，检测内存泄漏的工具常用的一般有三种，MS C-Runtime Library内建的检测功能；外挂式的检测工具，诸如，Purify，BoundsChecker等；利用Windows NT自带的Performance Monitor。这三种工具各有优缺点，MS C-Runtime Library虽然功能上较之外挂式的工具要弱，但是它是免费的；Performance Monitor虽然无法标示出发生问题的代码，但是它能检测出隐式的内存泄漏的存在，这是其他两类工具无能为力的地方。<br /><br />　　以下我们详细讨论这三种检测工具：<br /><br />　　VC下内存泄漏的检测方法<br /><br />　　用MFC开发的应用程序，在DEBUG版模式下编译后，都会自动加入内存泄漏的检测代码。在程序结束后，如果发生了内存泄漏，在Debug窗口中会显示出所有发生泄漏的内存块的信息，以下两行显示了一块被泄漏的内存块的信息：<br /><br />E:\TestMemLeak\TestDlg.cpp(70) : {59} normal block at 0x00881710, 200 bytes long.<br /><br />Data: &lt;abcdefghijklmnop&gt; 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70<br /><br />　　第一行显示该内存块由TestDlg.cpp文件，第70行代码分配，地址在0x00881710，大小为200字节，{59}是指调用内存分配函数的Request Order，关于它的详细信息可以参见MSDN中_CrtSetBreakAlloc()的帮助。第二行显示该内存块前16个字节的内容，尖括号内是以ASCII方式显示，接着的是以16进制方式显示。<br /><br />　　一般大家都误以为这些内存泄漏的检测功能是由MFC提供的，其实不然。MFC只是封装和利用了MS C-Runtime Library的Debug Function。非MFC程序也可以利用MS C-Runtime Library的Debug Function加入内存泄漏的检测功能。MS C-Runtime Library在实现malloc/free，strdup等函数时已经内建了内存泄漏的检测功能。<br /><br />　　注意观察一下由MFC Application Wizard生成的项目，在每一个cpp文件的头部都有这样一段宏定义：<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>#ifdef _DEBUG<br />#define new DEBUG_NEW<br />#undef THIS_FILE<br />static char THIS_FILE[] = __FILE__;<br />#endif</td></tr></tbody></table><br />　　有了这样的定义，在编译DEBUG版时，出现在这个cpp文件中的所有new都被替换成DEBUG_NEW了。那么DEBUG_NEW是什么呢？DEBUG_NEW也是一个宏，以下摘自afx.h，1632行<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>#define DEBUG_NEW new(THIS_FILE, __LINE__)</td></tr></tbody></table><br />　　所以如果有这样一行代码：<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>char* p = new char[200];</td></tr></tbody></table><br />　　经过宏替换就变成了：<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>char* p = new( THIS_FILE, __LINE__)char[200];</td></tr></tbody></table><br />　　根据C++的标准，对于以上的new的使用方法，编译器会去找这样定义的operator new：<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>void* operator new(size_t, LPCSTR, int)</td></tr></tbody></table><br />　　我们在afxmem.cpp 63行找到了一个这样的operator new 的实现<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine)<br />{<br />　return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine);<br />}<br /><br />void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine)<br />{<br />　&#8230;<br />　pResult = _malloc_dbg(nSize, nType, lpszFileName, nLine);<br />　if (pResult != NULL)<br />　　return pResult;<br />　&#8230;<br />}</td></tr></tbody></table><br />　　第二个operator new函数比较长，为了简单期间，我只摘录了部分。很显然最后的内存分配还是通过_malloc_dbg函数实现的，这个函数属于MS C-Runtime Library 的Debug Function。这个函数不但要求传入内存的大小，另外还有文件名和行号两个参数。文件名和行号就是用来记录此次分配是由哪一段代码造成的。如果这块内存在程序结束之前没有被释放，那么这些信息就会输出到Debug窗口里。<br /><br />　　这里顺便提一下THIS_FILE，__FILE和__LINE__。__FILE__和__LINE__都是编译器定义的宏。当碰到__FILE__时，编译器会把__FILE__替换成一个字符串，这个字符串就是当前在编译的文件的路径名。当碰到__LINE__时，编译器会把__LINE__替换成一个数字，这个数字就是当前这行代码的行号。在DEBUG_NEW的定义中没有直接使用__FILE__，而是用了THIS_FILE，其目的是为了减小目标文件的大小。假设在某个cpp文件中有100处使用了new，如果直接使用__FILE__，那编译器会产生100个常量字符串，这100个字符串都是飧?/SPAN&gt;cpp文件的路径名，显然十分冗余。如果使用THIS_FILE，编译器只会产生一个常量字符串，那100处new的调用使用的都是指向常量字符串的指针。<br /><br />　　再次观察一下由MFC Application Wizard生成的项目，我们会发现在cpp文件中只对new做了映射，如果你在程序中直接使用malloc函数分配内存，调用malloc的文件名和行号是不会被记录下来的。如果这块内存发生了泄漏，MS C-Runtime Library仍然能检测到，但是当输出这块内存块的信息，不会包含分配它的的文件名和行号。<br /><br />　　要在非MFC程序中打开内存泄漏的检测功能非常容易，你只要在程序的入口处加入以下几行代码：<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );<br /><br />tmpFlag |= _CRTDBG_LEAK_CHECK_DF;<br /><br />_CrtSetDbgFlag( tmpFlag );</td></tr></tbody></table><br />　　这样，在程序结束的时候，也就是winmain，main或dllmain函数返回之后，如果还有内存块没有释放，它们的信息会被打印到Debug窗口里。<br /><br />　　如果你试着创建了一个非MFC应用程序，而且在程序的入口处加入了以上代码，并且故意在程序中不释放某些内存块，你会在Debug窗口里看到以下的信息：<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>{47} normal block at 0x00C91C90, 200 bytes long.<br /><br />Data: &lt; &gt; 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F</td></tr></tbody></table><br />　　内存泄漏的确检测到了，但是和上面MFC程序的例子相比，缺少了文件名和行号。对于一个比较大的程序，没有这些信息，解决问题将变得十分困难。<br /><br />　　为了能够知道泄漏的内存块是在哪里分配的，你需要实现类似MFC的映射功能，把new，maolloc等函数映射到_malloc_dbg函数上。这里我不再赘述，你可以参考MFC的源代码。<br /><br />　　由于Debug Function实现在MS C-RuntimeLibrary中，所以它只能检测到堆内存的泄漏，而且只限于malloc，realloc或strdup等分配的内存，而那些系统资源，比如HANDLE，GDI Object，或是不通过C-Runtime Library分配的内存，比如VARIANT，BSTR的泄漏，它是无法检测到的，这是这种检测法的一个重大的局限性。另外，为了能记录内存块是在哪里分配的，源代码必须相应的配合，这在调试一些老的程序非常麻烦，毕竟修改源代码不是一件省心的事，这是这种检测法的另一个局限性。<br /><br />　　对于开发一个大型的程序，MS C-Runtime Library提供的检测功能是远远不够的。接下来我们就看看外挂式的检测工具。我用的比较多的是BoundsChecker，一则因为它的功能比较全面，更重要的是它的稳定性。这类工具如果不稳定，反而会忙里添乱。到底是出自鼎鼎大名的NuMega，我用下来基本上没有什么大问题。<br /><br />使用BoundsChecker检测内存泄漏：<br /><br />　　BoundsChecker采用一种被称为 Code Injection的技术，来截获对分配内存和释放内存的函数的调用。简单地说，当你的程序开始运行时，BoundsChecker的DLL被自动载入进程的地址空间（这可以通过system-level的Hook实现），然后它会修改进程中对内存分配和释放的函数调用，让这些调用首先转入它的代码，然后再执行原来的代码。BoundsChecker在做这些动作的时，无须修改被调试程序的源代码或工程配置文件，这使得使用它非常的简便、直接。<br /><br />　　这里我们以malloc函数为例，截获其他的函数方法与此类似。<br /><br />　　需要被截获的函数可能在DLL中，也可能在程序的代码里。比如，如果静态连结C-Runtime Library，那么malloc函数的代码会被连结到程序里。为了截获住对这类函数的调用，BoundsChecker会动态修改这些函数的指令。<br /><br />　　以下两段汇编代码，一段没有BoundsChecker介入，另一段则有BoundsChecker的介入：<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>126: _CRTIMP void * __cdecl malloc (<br />127: size_t nSize<br />128: )<br />129: {<br /><br />00403C10 push ebp<br />00403C11 mov ebp,esp<br />130: return _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, NULL, 0);<br />00403C13 push 0<br />00403C15 push 0<br />00403C17 push 1<br />00403C19 mov eax,[__newmode (0042376c)]<br />00403C1E push eax<br />00403C1F mov ecx,dword ptr [nSize]<br />00403C22 push ecx<br />00403C23 call _nh_malloc_dbg (00403c80)<br />00403C28 add esp,14h<br />131: }</td></tr></tbody></table><br />　　以下这一段代码有BoundsChecker介入：<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>126: _CRTIMP void * __cdecl malloc (<br />127: size_t nSize<br />128: )<br />129: {<br /><br />00403C10 jmp 01F41EC8<br />00403C15 push 0<br />00403C17 push 1<br />00403C19 mov eax,[__newmode (0042376c)]<br />00403C1E push eax<br />00403C1F mov ecx,dword ptr [nSize]<br />00403C22 push ecx<br />00403C23 call _nh_malloc_dbg (00403c80)<br />00403C28 add esp,14h<br />131: }</td></tr></tbody></table><br />　　当BoundsChecker介入后，函数malloc的前三条汇编指令被替换成一条jmp指令，原来的三条指令被搬到地址01F41EC8处了。当程序进入malloc后先jmp到01F41EC8，执行原来的三条指令，然后就是BoundsChecker的天下了。大致上它会先记录函数的返回地址（函数的返回地址在stack上，所以很容易修改），然后把返回地址指向属于BoundsChecker的代码，接着跳到malloc函数原来的指令，也就是在00403c15的地方。当malloc函数结束的时候，由于返回地址被修改，它会返回到BoundsChecker的代码中，此时BoundsChecker会记录由malloc分配的内存的指针，然后再跳转到到原来的返回地址去。<br /><br />　　如果内存分配/释放函数在DLL中，BoundsChecker则采用另一种方法来截获对这些函数的调用。BoundsChecker通过修改程序的DLL Import Table让table中的函数地址指向自己的地址，以达到截获的目的。<br /><br />　　截获住这些分配和释放函数，BoundsChecker就能记录被分配的内存或资源的生命周期。接下来的问题是如何与源代码相关，也就是说当BoundsChecker检测到内存泄漏，它如何报告这块内存块是哪段代码分配的。答案是调试信息（Debug Information）。当我们编译一个Debug版的程序时，编译器会把源代码和二进制代码之间的对应关系记录下来，放到一个单独的文件里(.pdb)或者直接连结进目标程序，通过直接读取调试信息就能得到分配某块内存的源代码在哪个文件，哪一行上。使用Code Injection和Debug Information，使BoundsChecker不但能记录呼叫分配函数的源代码的位置，而且还能记录分配时的Call Stack，以及Call Stack上的函数的源代码位置。这在使用像MFC这样的类库时非常有用，以下我用一个例子来说明：<br /><br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>void ShowXItemMenu()<br />{<br />　&#8230;<br />　CMenu menu;<br /><br />　menu.CreatePopupMenu();<br />　//add menu items.<br />　menu.TrackPropupMenu();<br />　&#8230;<br />}<br /><br />void ShowYItemMenu( )<br />{<br />　&#8230;<br />　CMenu menu;<br />　menu.CreatePopupMenu();<br />　//add menu items.<br />　menu.TrackPropupMenu();<br />　menu.Detach();//this will cause HMENU leak<br />　&#8230;<br />}<br /><br />BOOL CMenu::CreatePopupMenu()<br />{<br />　&#8230;<br />　hMenu = CreatePopupMenu();<br />　&#8230;<br />}</td></tr></tbody></table><br />　　当调用ShowYItemMenu()时，我们故意造成HMENU的泄漏。但是，对于BoundsChecker来说被泄漏的HMENU是在class CMenu::CreatePopupMenu()中分配的。假设的你的程序有许多地方使用了CMenu的CreatePopupMenu()函数，如CMenu::CreatePopupMenu()造成的，你依然无法确认问题的根结到底在哪里，在ShowXItemMenu()中还是在ShowYItemMenu()中，或者还有其它的地方也使用了CreatePopupMenu()？有了Call Stack的信息，问题就容易了。BoundsChecker会如下报告泄漏的HMENU的信息：<br /><br />
<table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1">
<tbody>
<tr>
<td>Function<br />File<br />Line<br /><br />CMenu::CreatePopupMenu<br />E:\8168\vc98\mfc\mfc\include\afxwin1.inl<br />1009<br /><br />ShowYItemMenu<br />E:\testmemleak\mytest.cpp<br />100</td></tr></tbody></table><br />　　这里省略了其他的函数调用<br /><br />　　如此，我们很容易找到发生问题的函数是ShowYItemMenu()。当使用MFC之类的类库编程时，大部分的API调用都被封装在类库的class里，有了Call Stack信息，我们就可以非常容易的追踪到真正发生泄漏的代码。<br /><br />　　记录Call Stack信息会使程序的运行变得非常慢，因此默认情况下BoundsChecker不会记录Call Stack信息。可以按照以下的步骤打开记录Call Stack信息的选项开关：<br /><br />　　1. 打开菜单：BoundsChecker|Setting&#8230; <br /><br />　　2. 在Error Detection页中，在Error Detection Scheme的List中选择Custom<br /><br />　　3. 在Category的Combox中选择 Pointer and leak error check<br /><br />　　4. 钩上Report Call Stack复选框<br /><br />　　5. 点击Ok<br /><br />　　基于Code Injection，BoundsChecker还提供了API Parameter的校验功能，memory over run等功能。这些功能对于程序的开发都非常有益。由于这些内容不属于本文的主题，所以不在此详述了。<br /><br />　　尽管BoundsChecker的功能如此强大，但是面对隐式内存泄漏仍然显得苍白无力。所以接下来我们看看如何用Performance Monitor检测内存泄漏。<br /><br />　　使用Performance Monitor检测内存泄漏<br /><br />　　NT的内核在设计过程中已经加入了系统监视功能，比如CPU的使用率，内存的使用情况，I/O操作的频繁度等都作为一个个Counter，应用程序可以通过读取这些Counter了解整个系统的或者某个进程的运行状况。Performance Monitor就是这样一个应用程序。<br /><br />　　为了检测内存泄漏，我们一般可以监视Process对象的Handle Count，Virutal Bytes 和Working Set三个Counter。Handle Count记录了进程当前打开的HANDLE的个数，监视这个Counter有助于我们发现程序是否有Handle泄漏；Virtual Bytes记录了该进程当前在虚地址空间上使用的虚拟内存的大小，NT的内存分配采用了两步走的方法，首先，在虚地址空间上保留一段空间，这时操作系统并没有分配物理内存，只是保留了一段地址。然后，再提交这段空间，这时操作系统才会分配物理内存。所以，Virtual Bytes一般总大于程序的Working Set。监视Virutal Bytes可以帮助我们发现一些系统底层的问题; Working Set记录了操作系统为进程已提交的内存的总量，这个值和程序申请的内存总量存在密切的关系，如果程序存在内存的泄漏这个值会持续增加，但是Virtual Bytes却是跳跃式增加的。<br /><br />　　监视这些Counter可以让我们了解进程使用内存的情况，如果发生了泄漏，即使是隐式内存泄漏，这些Counter的值也会持续增加。但是，我们知道有问题却不知道哪里有问题，所以一般使用Performance Monitor来验证是否有内存泄漏，而使用BoundsChecker来找到和解决。<br /><br />　　当Performance Monitor显示有内存泄漏，而BoundsChecker却无法检测到，这时有两种可能：第一种，发生了偶发性内存泄漏。这时你要确保使用Performance Monitor和使用BoundsChecker时，程序的运行环境和操作方法是一致的。第二种，发生了隐式的内存泄漏。这时你要重新审查程序的设计，然后仔细研究Performance Monitor记录的Counter的值的变化图，分析其中的变化和程序运行逻辑的关系，找到一些可能的原因。这是一个痛苦的过程，充满了假设、猜想、验证、失败，但这也是一个积累经验的绝好机会。<br /><br />　　总结<br /><br />　　内存泄漏是个大而复杂的问题，即使是Java和.Net这样有Gabarge Collection机制的环境，也存在着泄漏的可能，比如隐式内存泄漏。由于篇幅和能力的限制，本文只能对这个主题做一个粗浅的研究。其他的问题，比如多模块下的泄漏检测，如何在程序运行时对内存使用情况进行分析等等，都是可以深入研究的题目。如果您有什么想法，建议或发现了某些错误，欢迎和我交流。<br /><br /><a href="http://dev.yesky.com/147/2356147_1.shtml">http://dev.yesky.com/147/2356147_1.shtml</a><img src ="http://www.cppblog.com/yehao/aggbug/157969.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-10-10 15:09 <a href="http://www.cppblog.com/yehao/articles/157969.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C/C++内存泄漏及检测</title><link>http://www.cppblog.com/yehao/articles/157967.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Mon, 10 Oct 2011 06:59:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/157967.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/157967.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/157967.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/157967.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/157967.html</trackback:ping><description><![CDATA[<p>&#8220;该死系统存在内存泄漏问题&#8221;，项目中由于各方面因素，总是有人抱怨存在内存泄漏，系统长时间运行之后，可用内存越来越少，甚至导致了某些服务失败。内存泄漏是最难发现的常见错误之一，因为除非用完内存或调用malloc失败，否则都不会导致任何问题。实际上，使用C/C++这类没有垃圾回收机制的语言时，你很多时间都花在处理如何正确释放内存上。如果程序运行时间足够长，如后台进程运行在服务器上，只要服务器不宕机就一直运行，一个小小的失误也会对程序造成重大的影响，如造成某些关键服务失败。</p>
<p>对于内存泄漏，本人深有体会！实习的时候，公司一个项目中就存在内存泄漏问题，项目的代码两非常大，后台进程也比较多，造成内存泄漏的地方比较难找。这次机会是我对如何查找内存泄漏问题，有了一定的经验，后面自己的做了相关实验，在此我分享一下内存泄漏如何调试查找，主要内容如下：</p>
<ul><li>1、内存泄漏简介</li><li>2、Windows平台下的内存泄漏检测 
<ul><li>2.1、检测是否存在内存泄漏问题</li><li>2.2、定位具体的内存泄漏地方 </li></ul></li><li>3、Linux平台下的内存泄漏检测&nbsp;</li><li>4、总结 </li></ul>
<p>其实Windows、Linux下面的内存检测都可以单独开篇详细介绍，方法和工具也远远不止文中介绍到的，我的方法也不是最优的，如果您有更好的方法，也请您告诉我和大家。</p>
<h1></h1>
<h1>1、内存泄漏简介及后果</h1>
<p>wikipedia中这样定义内存泄漏：在计算机科学中，内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失，而是应用程序分配某段内存后，由于设计错误，导致在释放该段内存之前就失去了对该段内存的控制，从而造成了内存的浪费。</p>
<p>最难捉摸也最难检测到的错误之一是内存泄漏，即未能正确释放以前分配的内存的 bug。 只发生一次的小的内存泄漏可能不会被注意，但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各种征兆：从性能不良（并且逐渐降低）到内存完全用尽。 更糟的是，泄漏的程序可能会用掉太多内存，以致另一个程序失败，而使用户无从查找问题的真正根源。 此外，即使无害的内存泄漏也可能是其他问题的征兆。</p>
<p>内存泄漏会因为减少可用内存的数量从而降低计算机的性能。最终，在最糟糕的情况下，过多的可用内存被分配掉导致全部或部分设备停止正常工作，或者应用程序崩溃。内存泄漏可能不严重，甚至能够被常规的手段检测出来。在现代操作系统中，一个应用程序使用的常规内存在程序终止时被释放。这表示一个短暂运行的应用程序中的内存泄漏不会导致严重后果。</p>
<p>在以下情況，内存泄漏导致较严重的后果：</p>
<ul><li>程序运行后置之不理，并且随着时间的流失消耗越来越多的内存（比如服务器上的后台任务，尤其是<a href="http://zh.wikipedia.org/wiki/%E5%B5%8C%E5%85%A5%E5%BC%8F%E7%B3%BB%E7%BB%9F">嵌入式系统</a>中的后台任务，这些任务可能被运行后很多年内都置之不理）；</li><li>新的内存被频繁地分配，比如当显示电脑游戏或动画视频画面时；</li><li>程序能够请求未被释放的内存（比如<a href="http://zh.wikipedia.org/wiki/%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98">共享内存</a>），甚至是在程序终止的时候；</li><li>泄漏在操作系统内部发生；</li><li>泄漏在系统关键驱动中发生；</li><li>内存非常有限，比如在<a href="http://zh.wikipedia.org/wiki/%E5%B5%8C%E5%85%A5%E5%BC%8F%E7%B3%BB%E7%BB%9F">嵌入式系统</a>或便携设备中；</li><li>当运行于一个终止时内存并不自动释放的操作系统（比如<a href="http://zh.wikipedia.org/w/index.php?title=AmigaOS&amp;action=edit&amp;redlink=1">AmigaOS</a>）之上，而且一旦丢失只能通过重启来恢复。 </li></ul>
<p>下面我们通过以下例子来介绍如何检测内存泄漏问题：</p>
<div class="syntaxhighlighter  cpp" id="highlighter_159889">
<div class="bar              ">
<div class="toolbar"><a class="item viewSource" title="view source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#viewSource" highlighterid="highlighter_159889" commandname="viewSource">view source</a><a class="item printSource" title="print" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#printSource" highlighterid="highlighter_159889" commandname="printSource">print</a><a class="item about" title="?" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#about" highlighterid="highlighter_159889" commandname="about">?</a></div></div>
<div class="lines">
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>01</code></td>
<td class="content"><code class="cpp preprocessor">#include &lt;stdlib.h&gt; </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>02</code></td>
<td class="content"><code class="cpp preprocessor">#include &lt;iostream&gt; </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>03</code></td>
<td class="content"><code class="cpp keyword bold">using</code> <code class="cpp keyword bold">namespace</code> <code class="cpp plain">std; </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>04</code></td>
<td class="content"><code class="spaces">&nbsp;</code>&nbsp;</td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>05</code></td>
<td class="content"><code class="cpp keyword bold">void</code> <code class="cpp plain">GetMemory(</code><code class="cpp color1 bold">char</code> <code class="cpp plain">*p, </code><code class="cpp color1 bold">int</code> <code class="cpp plain">num) </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>06</code></td>
<td class="content"><code class="cpp plain">{ </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>07</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">p = (</code><code class="cpp color1 bold">char</code><code class="cpp plain">*)</code><code class="cpp functions bold">malloc</code><code class="cpp plain">(</code><code class="cpp keyword bold">sizeof</code><code class="cpp plain">(</code><code class="cpp color1 bold">char</code><code class="cpp plain">) * num);</code><code class="cpp comments">//使用new也能够检测出来 </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>08</code></td>
<td class="content"><code class="cpp plain">} </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>09</code></td>
<td class="content"><code class="spaces">&nbsp;</code>&nbsp;</td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>10</code></td>
<td class="content"><code class="cpp color1 bold">int</code> <code class="cpp plain">main(</code><code class="cpp color1 bold">int</code> <code class="cpp plain">argc,</code><code class="cpp color1 bold">char</code><code class="cpp plain">** argv) </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>11</code></td>
<td class="content"><code class="cpp plain">{ </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>12</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp color1 bold">char</code> <code class="cpp plain">*str = NULL; </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>13</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">GetMemory(str, 100); </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>14</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">cout&lt;&lt;</code><code class="cpp string">"Memory leak test!"</code><code class="cpp plain">&lt;&lt;endl; </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>15</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp comments">//如果main中存在while循环调用GetMemory </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>16</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp comments">//那么问题将变得很严重 </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>17</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp comments">//while(1){GetMemory(...);} </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>18</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp keyword bold">return</code> <code class="cpp plain">0; </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>19</code></td>
<td class="content"><code class="cpp plain">}</code></td></tr></tbody></table></div></div></div>
<p>实际中不可能这么简单，如果这么简单也用不着别的方法，程序员一眼就可以看出问题，此程序只用于测试。</p>
<h1>2、Windows平台下的内存泄漏检测</h1>
<h2>2.1、检测是否存在内存泄漏问题</h2>
<p>Windows平台下面Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法，原理大致如下：内存分配要通过CRT在运行时实现，只要在分配内存和释放内存时分别做好记录，程序结束时对比分配内存和释放内存的记录就可以确定是不是有内存泄漏。在vs中启用内存检测的方法如下：</p>
<ul><li>STEP1，在程序中包括以下语句： （#include 语句必须采用上文所示顺序。 如果更改了顺序，所使用的函数可能无法正常工作。） </li></ul>
<div class="syntaxhighlighter  cpp" id="highlighter_177436">
<div class="bar                 ">
<div class="toolbar"><a class="item viewSource" title="view source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#viewSource" highlighterid="highlighter_177436" commandname="viewSource">view source</a><a class="item printSource" title="print" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#printSource" highlighterid="highlighter_177436" commandname="printSource">print</a><a class="item about" title="?" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#about" highlighterid="highlighter_177436" commandname="about">?</a></div></div>
<div class="lines">
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>1</code></td>
<td class="content"><code class="cpp preprocessor">#define _CRTDBG_MAP_ALLOC </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>2</code></td>
<td class="content"><code class="cpp preprocessor">#include &lt;stdlib.h&gt; </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>3</code></td>
<td class="content"><code class="cpp preprocessor">#include &lt;crtdbg.h&gt;</code></td></tr></tbody></table></div></div></div>
<p>通过包括 crtdbg.h，将 <a href="http://msdn.microsoft.com/zh-cn/library/6ewkz86d.aspx">malloc</a> 和 <a href="http://msdn.microsoft.com/zh-cn/library/we1whae7.aspx">free</a> 函数映射到它们的调试版本，即 <a href="http://msdn.microsoft.com/zh-cn/library/faz3a37z.aspx">_malloc_dbg</a> 和 <a href="http://msdn.microsoft.com/zh-cn/library/16swbsbc.aspx">_free_dbg</a>，这两个函数将跟踪内存分配和释放。 此映射只在调试版本（在其中定义了<strong>_DEBUG</strong>）中发生。 发布版本使用普通的 <strong>malloc</strong> 和 <strong>free</strong> 函数。</p>
<p>#define 语句将 CRT 堆函数的基版本映射到对应的&#8220;Debug&#8221;版本。 并非绝对需要该语句；但如果没有该语句，内存泄漏转储包含的有用信息将较少。</p>
<ul><li>STEP2， 在添加了上述语句之后，可以通过在程序中包括以下语句（通常应恰好放在程序退出位置之前）来转储内存泄漏信息： </li></ul>
<div class="syntaxhighlighter  cpp" id="highlighter_901173">
<div class="bar                                    ">
<div class="toolbar"><a class="item viewSource" title="view source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#viewSource" highlighterid="highlighter_901173" commandname="viewSource">view source</a><a class="item printSource" title="print" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#printSource" highlighterid="highlighter_901173" commandname="printSource">print</a><a class="item about" title="?" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#about" highlighterid="highlighter_901173" commandname="about">?</a></div></div>
<div class="lines">
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>1</code></td>
<td class="content"><code class="cpp plain">_CrtDumpMemoryLeaks();</code></td></tr></tbody></table></div></div></div>
<p>此时，完整的代码如下：</p>
<div class="syntaxhighlighter collapsed  cpp" id="highlighter_113573">
<div class="bar            ">
<div class="toolbar"><a class="item expandSource" title="show source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#expandSource" highlighterid="highlighter_113573" commandname="expandSource">show source</a><a class="item viewSource" title="view source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#viewSource" highlighterid="highlighter_113573" commandname="viewSource">view source</a><a class="item printSource" title="print" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#printSource" highlighterid="highlighter_113573" commandname="printSource">print</a><a class="item about" title="?" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#about" highlighterid="highlighter_113573" commandname="about">?</a></div></div>
<div class="lines">
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>01</code></td>
<td class="content"><code class="cpp preprocessor">#define _CRTDBG_MAP_ALLOC </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>02</code></td>
<td class="content"><code class="cpp preprocessor">#include &lt;stdlib.h&gt; </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>03</code></td>
<td class="content"><code class="cpp preprocessor">#include &lt;crtdbg.h&gt; </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>04</code></td>
<td class="content"><code class="spaces">&nbsp;</code>&nbsp;</td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>05</code></td>
<td class="content"><code class="cpp preprocessor">#include &lt;iostream&gt; </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>06</code></td>
<td class="content"><code class="cpp keyword bold">using</code> <code class="cpp keyword bold">namespace</code> <code class="cpp plain">std; </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>07</code></td>
<td class="content"><code class="spaces">&nbsp;</code>&nbsp;</td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>08</code></td>
<td class="content"><code class="cpp keyword bold">void</code> <code class="cpp plain">GetMemory(</code><code class="cpp color1 bold">char</code> <code class="cpp plain">*p, </code><code class="cpp color1 bold">int</code> <code class="cpp plain">num) </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>09</code></td>
<td class="content"><code class="cpp plain">{ </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>10</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">p = (</code><code class="cpp color1 bold">char</code><code class="cpp plain">*)</code><code class="cpp functions bold">malloc</code><code class="cpp plain">(</code><code class="cpp keyword bold">sizeof</code><code class="cpp plain">(</code><code class="cpp color1 bold">char</code><code class="cpp plain">) * num); </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>11</code></td>
<td class="content"><code class="cpp plain">} </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>12</code></td>
<td class="content"><code class="spaces">&nbsp;</code>&nbsp;</td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>13</code></td>
<td class="content"><code class="cpp color1 bold">int</code> <code class="cpp plain">main(</code><code class="cpp color1 bold">int</code> <code class="cpp plain">argc,</code><code class="cpp color1 bold">char</code><code class="cpp plain">** argv) </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>14</code></td>
<td class="content"><code class="cpp plain">{ </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>15</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp color1 bold">char</code> <code class="cpp plain">*str = NULL; </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>16</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">GetMemory(str, 100); </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>17</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">cout&lt;&lt;</code><code class="cpp string">"Memory leak test!"</code><code class="cpp plain">&lt;&lt;endl; </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>18</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">_CrtDumpMemoryLeaks(); </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>19</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp keyword bold">return</code> <code class="cpp plain">0; </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>20</code></td>
<td class="content"><code class="cpp plain">}</code></td></tr></tbody></table></div></div></div>
<p>当在调试器下运行程序时，<a href="http://msdn.microsoft.com/zh-cn/library/d41t22sb.aspx">_CrtDumpMemoryLeaks</a> 将在<a href="http://msdn.microsoft.com/zh-cn/library/3hk6fby3.aspx">&#8220;输出&#8221;窗口</a>中显示内存泄漏信息。 内存泄漏信息如下所示： </p>
<p><a href="http://images.cnblogs.com/cnblogs_com/skynet/201102/20110220175032138.png"><img title="image" style="border-top-width: 0px; padding-right: 0px; display: inline; padding-left: 0px; border-left-width: 0px;background-image: none; border-bottom-width: 0px; padding-top: 0px; border-right-width: 0px" height="93" alt="image" src="http://images.cnblogs.com/cnblogs_com/skynet/201102/20110220175032977.png" width="771" border="0" /></a></p>
<p>如果没有使用 #define _CRTDBG_MAP_ALLOC 语句，内存泄漏转储将如下所示：</p>
<p><a href="http://images.cnblogs.com/cnblogs_com/skynet/201102/201102201750331467.png"><img title="image" style="border-top-width: 0px; padding-right: 0px; display: inline; padding-left: 0px; border-left-width: 0px;background-image: none; border-bottom-width: 0px; padding-top: 0px; border-right-width: 0px" height="99" alt="image" src="http://images.cnblogs.com/cnblogs_com/skynet/201102/201102201750332863.png" width="468" border="0" /></a></p>
<p>未定义 _CRTDBG_MAP_ALLOC 时，所显示的会是：</p>
<ul><li>
<p>内存分配编号（在大括号内）。</p></li><li>
<p><a href="http://msdn.microsoft.com/zh-cn/library/htdyz80k.aspx">块类型</a>（普通、客户端或 CRT）。</p></li></ul>
<blockquote>
<ul><li>
<p>&#8220;普通块&#8221;是由程序分配的普通内存。</p></li><li>
<p>&#8220;客户端块&#8221;是由 MFC 程序用于需要析构函数的对象的特殊类型内存块。 MFC new 操作根据正在创建的对象的需要创建普通块或客户端块。</p></li><li>
<p>&#8220;CRT 块&#8221;是由 CRT 库为自己使用而分配的内存块。 CRT 库处理这些块的释放，因此您不大可能在内存泄漏报告中看到这些块，除非出现严重错误（例如 CRT 库损坏）。</p></li></ul>
<p>从不会在内存泄漏信息中看到下面两种块类型：</p>
<ul><li>
<p>&#8220;可用块&#8221;是已释放的内存块。</p></li><li>
<p>&#8220;忽略块&#8221;是您已特别标记的块，因而不出现在内存泄漏报告中。</p></li></ul></blockquote>
<ul><li>
<p>十六进制形式的内存位置。</p></li><li>
<p>以字节为单位的块大小。</p></li><li>
<p>前 16 字节的内容（亦为十六进制）。</p></li></ul>
<p>定义了 _CRTDBG_MAP_ALLOC 时，还会显示在其中分配泄漏的内存的文件。 文件名后括号中的数字（本示例中为 10）是该文件中的行号。</p>
<p><span style="font-size: medium" size="4">注意：如果程序总是在同一位置退出，调用 <a href="http://msdn.microsoft.com/zh-cn/library/d41t22sb.aspx">_CrtDumpMemoryLeaks</a> 将非常容易。 如果程序从多个位置退出，则无需在每个可能退出的位置放置对 <strong>_CrtDumpMemoryLeaks</strong> 的调用，而可以在程序开始处包含以下调用：</span></p>
<div class="syntaxhighlighter collapsed  cpp" id="highlighter_624428">
<div class="bar                      ">
<div class="toolbar"><a class="item expandSource" title="show source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#expandSource" highlighterid="highlighter_624428" commandname="expandSource">show source</a><a class="item viewSource" title="view source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#viewSource" highlighterid="highlighter_624428" commandname="viewSource">view source</a><a class="item printSource" title="print" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#printSource" highlighterid="highlighter_624428" commandname="printSource">print</a><a class="item about" title="?" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#about" highlighterid="highlighter_624428" commandname="about">?</a></div></div>
<div class="lines">
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>1</code></td>
<td class="content"><code class="cpp plain">_CrtSetDbgFlag ( <span id="TheWorldHiLightStyleID" style="background: #ffff00; color: #000000">_CRTDBG_ALLOC_MEM_DF</span> | _CRTDBG_LEAK_CHECK_DF );</code></td></tr></tbody></table></div></div></div>
<p><span style="font-size: medium" size="4">该语句在程序退出时自动调用 <strong>_CrtDumpMemoryLeaks</strong>。 必须同时设置 <strong><span id="TheWorldHiLightStyleID" style="background: #ffff00; color: #000000">_CRTDBG_ALLOC_MEM_DF</span></strong> 和 <strong>_CRTDBG_LEAK_CHECK_DF</strong> 两个位域，如前面所示。</span></p>
<h2>2.2、定位具体的内存泄漏地方</h2>
<p>通过上面的方法，我们几乎可以定位到是哪个地方调用内存分配函数malloc和new等，如上例中的GetMemory函数中，即第10行！但是不能定位到，在哪个地方调用GetMemory()导致的内存泄漏，而且在大型项目中可能有很多处调用GetMemory。如何要定位到在哪个地方调用GetMemory导致的内存泄漏？</p>
<p>定位内存泄漏的另一种技术涉及在关键点对应用程序的内存状态拍快照。 CRT 库提供一种结构类型 <strong>_CrtMemState</strong>，您可用它存储内存状态的快照：</p>
<div class="syntaxhighlighter  cpp" id="highlighter_411356">
<div class="bar          ">
<div class="toolbar"><a class="item viewSource" title="view source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#viewSource" highlighterid="highlighter_411356" commandname="viewSource">view source</a><a class="item printSource" title="print" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#printSource" highlighterid="highlighter_411356" commandname="printSource">print</a><a class="item about" title="?" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#about" highlighterid="highlighter_411356" commandname="about">?</a></div></div>
<div class="lines">
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>1</code></td>
<td class="content"><code class="cpp plain">_CrtMemState s1, s2, s3;</code></td></tr></tbody></table></div></div></div>
<p>若要在给定点对内存状态拍快照，请向 <a href="http://msdn.microsoft.com/zh-cn/library/h3z85t43.aspx">_CrtMemCheckpoint</a> 函数传递 <strong>_CrtMemState</strong> 结构。 该函数用当前内存状态的快照填充此结构：</p>
<div class="syntaxhighlighter  cpp" id="highlighter_44340">
<div class="bar       ">
<div class="toolbar"><a class="item viewSource" title="view source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#viewSource" highlighterid="highlighter_44340" commandname="viewSource">view source</a><a class="item printSource" title="print" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#printSource" highlighterid="highlighter_44340" commandname="printSource">print</a><a class="item about" title="?" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#about" highlighterid="highlighter_44340" commandname="about">?</a></div></div>
<div class="lines">
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>1</code></td>
<td class="content"><code class="cpp plain">_CrtMemCheckpoint( &amp;s1 );</code></td></tr></tbody></table></div></div></div>
<p>通过向 <a href="http://msdn.microsoft.com/zh-cn/library/swh3417y.aspx">_CrtMemDumpStatistics</a> 函数传递 <strong>_CrtMemState</strong> 结构，可以在任意点转储该结构的内容：</p>
<div class="syntaxhighlighter  cpp" id="highlighter_524791">
<div class="bar  ">
<div class="toolbar"><a class="item viewSource" title="view source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#viewSource" highlighterid="highlighter_524791" commandname="viewSource">view source</a><a class="item printSource" title="print" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#printSource" highlighterid="highlighter_524791" commandname="printSource">print</a><a class="item about" title="?" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#about" highlighterid="highlighter_524791" commandname="about">?</a></div></div>
<div class="lines">
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>1</code></td>
<td class="content"><code class="cpp plain">_CrtMemDumpStatistics( &amp;s1 );</code></td></tr></tbody></table></div></div></div>
<p>若要确定代码中某一部分是否发生了内存泄漏，可以在该部分之前和之后对内存状态拍快照，然后使用 <a href="http://msdn.microsoft.com/zh-cn/library/k4htzb06.aspx">_CrtMemDifference</a> 比较这两个状态：</p>
<div class="syntaxhighlighter  cpp" id="highlighter_519326">
<div class="bar      ">
<div class="toolbar"><a class="item viewSource" title="view source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#viewSource" highlighterid="highlighter_519326" commandname="viewSource">view source</a><a class="item printSource" title="print" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#printSource" highlighterid="highlighter_519326" commandname="printSource">print</a><a class="item about" title="?" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#about" highlighterid="highlighter_519326" commandname="about">?</a></div></div>
<div class="lines">
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>1</code></td>
<td class="content"><code class="cpp plain">_CrtMemCheckpoint( &amp;s1 ); </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>2</code></td>
<td class="content"><code class="cpp comments">// memory allocations take place here </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>3</code></td>
<td class="content"><code class="cpp plain">_CrtMemCheckpoint( &amp;s2 ); </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>4</code></td>
<td class="content"><code class="spaces">&nbsp;</code>&nbsp;</td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>5</code></td>
<td class="content"><code class="cpp keyword bold">if</code> <code class="cpp plain">( _CrtMemDifference( &amp;s3, &amp;s1, &amp;s2) ) </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>6</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">_CrtMemDumpStatistics( &amp;s3 );</code></td></tr></tbody></table></div></div></div>
<p>顾名思义，<strong>_CrtMemDifference</strong> 比较两个内存状态（s1 和 s2），生成这两个状态之间差异的结果（s3）。 在程序的开始和结尾放置 <strong>_CrtMemCheckpoint</strong> 调用，并使用<strong>_CrtMemDifference</strong> 比较结果，是检查内存泄漏的另一种方法。 如果检测到泄漏，则可以使用 <strong>_CrtMemCheckpoint</strong> 调用通过二进制搜索技术来划分程序和定位泄漏。</p>
<p>如上面的例子程序我们可以这样来定位确切的调用GetMemory的地方：</p>
<div class="syntaxhighlighter collapsed  cpp" id="highlighter_371918">
<div class="bar   ">
<div class="toolbar"><a class="item expandSource" title="show source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#expandSource" highlighterid="highlighter_371918" commandname="expandSource">show source</a><a class="item viewSource" title="view source" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#viewSource" highlighterid="highlighter_371918" commandname="viewSource">view source</a><a class="item printSource" title="print" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#printSource" highlighterid="highlighter_371918" commandname="printSource">print</a><a class="item about" title="?" style="width: 16px; height: 16px" href="http://www.cnblogs.com/skynet/archive/2011/02/20/1959162.html#about" highlighterid="highlighter_371918" commandname="about">?</a></div></div>
<div class="lines">
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>01</code></td>
<td class="content"><code class="cpp preprocessor">#define _CRTDBG_MAP_ALLOC </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>02</code></td>
<td class="content"><code class="cpp preprocessor">#include &lt;stdlib.h&gt; </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>03</code></td>
<td class="content"><code class="cpp preprocessor">#include &lt;crtdbg.h&gt; </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>04</code></td>
<td class="content"><code class="spaces">&nbsp;</code>&nbsp;</td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>05</code></td>
<td class="content"><code class="cpp preprocessor">#include &lt;iostream&gt; </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>06</code></td>
<td class="content"><code class="cpp keyword bold">using</code> <code class="cpp keyword bold">namespace</code> <code class="cpp plain">std; </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>07</code></td>
<td class="content"><code class="spaces">&nbsp;</code>&nbsp;</td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>08</code></td>
<td class="content"><code class="cpp plain">_CrtMemState s1, s2, s3; </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>09</code></td>
<td class="content"><code class="spaces">&nbsp;</code>&nbsp;</td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>10</code></td>
<td class="content"><code class="cpp keyword bold">void</code> <code class="cpp plain">GetMemory(</code><code class="cpp color1 bold">char</code> <code class="cpp plain">*p, </code><code class="cpp color1 bold">int</code> <code class="cpp plain">num) </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>11</code></td>
<td class="content"><code class="cpp plain">{ </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>12</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">p = (</code><code class="cpp color1 bold">char</code><code class="cpp plain">*)</code><code class="cpp functions bold">malloc</code><code class="cpp plain">(</code><code class="cpp keyword bold">sizeof</code><code class="cpp plain">(</code><code class="cpp color1 bold">char</code><code class="cpp plain">) * num); </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>13</code></td>
<td class="content"><code class="cpp plain">} </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>14</code></td>
<td class="content"><code class="spaces">&nbsp;</code>&nbsp;</td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>15</code></td>
<td class="content"><code class="cpp color1 bold">int</code> <code class="cpp plain">main(</code><code class="cpp color1 bold">int</code> <code class="cpp plain">argc,</code><code class="cpp color1 bold">char</code><code class="cpp plain">** argv) </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>16</code></td>
<td class="content"><code class="cpp plain">{ </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>17</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">_CrtMemCheckpoint( &amp;s1 ); </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>18</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp color1 bold">char</code> <code class="cpp plain">*str = NULL; </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>19</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">GetMemory(str, 100); </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>20</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">_CrtMemCheckpoint( &amp;s2 ); </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>21</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp keyword bold">if</code> <code class="cpp plain">( _CrtMemDifference( &amp;s3, &amp;s1, &amp;s2) ) </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>22</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">_CrtMemDumpStatistics( &amp;s3 ); </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>23</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">cout&lt;&lt;</code><code class="cpp string">"Memory leak test!"</code><code class="cpp plain">&lt;&lt;endl; </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>24</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp plain">_CrtDumpMemoryLeaks(); </code></td></tr></tbody></table></div>
<div class="line alt1">
<table>
<tbody>
<tr>
<td class="number"><code>25</code></td>
<td class="content"><code class="spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="cpp keyword bold">return</code> <code class="cpp plain">0; </code></td></tr></tbody></table></div>
<div class="line alt2">
<table>
<tbody>
<tr>
<td class="number"><code>26</code></td>
<td class="content"><code class="cpp plain">}</code></td></tr></tbody></table></div></div></div>
<p>调试时，程序输出如下结果：</p>
<p><a href="http://images.cnblogs.com/cnblogs_com/skynet/201102/201102201750335022.png"><img title="image" style="border-top-width: 0px; padding-right: 0px; display: inline; padding-left: 0px; border-left-width: 0px;background-image: none; border-bottom-width: 0px; padding-top: 0px; border-right-width: 0px" height="201" alt="image" src="http://images.cnblogs.com/cnblogs_com/skynet/201102/201102201750331958.png" width="773" border="0" /></a></p>
<p>这说明在s1和s2之间存在内存泄漏！！！如果GetMemory不是在s1和s2之间调用，那么就不会有信息输出。</p>
<h1>3、Linux平台下的内存泄漏检测</h1>
<p>在上面我们介绍了，vs中在代码中&#8220;包含crtdbg.h，将 <a href="http://msdn.microsoft.com/zh-cn/library/6ewkz86d.aspx">malloc</a> 和 <a href="http://msdn.microsoft.com/zh-cn/library/we1whae7.aspx">free</a> 函数映射到它们的调试版本，即 <a href="http://msdn.microsoft.com/zh-cn/library/faz3a37z.aspx">_malloc_dbg</a> 和 <a href="http://msdn.microsoft.com/zh-cn/library/16swbsbc.aspx">_free_dbg</a>，这两个函数将跟踪内存分配和释放。 此映射只在调试版本（在其中定义了<strong>_DEBUG</strong>）中发生。 发布版本使用普通的 <strong>malloc</strong> 和 <strong>free</strong> 函数。&#8221;即为malloc和free做了钩子，用于记录内存分配信息。</p>
<p>Linux下面也有原理相同的方法&#8212;&#8212;mtrace，<a href="http://en.wikipedia.org/wiki/Mtrace">http://en.wikipedia.org/wiki/Mtrace</a>。方法类似，我这就不具体描述，参加给出的链接。这节我主要介绍一个非常强大的工具valgrind。如下图所示：</p>
<p><a href="http://images.cnblogs.com/cnblogs_com/skynet/201102/201102201750352698.png"><img title="image" style="border-top-width: 0px; padding-right: 0px; display: inline; padding-left: 0px; border-left-width: 0px;background-image: none; border-bottom-width: 0px; padding-top: 0px; border-right-width: 0px" height="466" alt="image" src="http://images.cnblogs.com/cnblogs_com/skynet/201102/201102201750364202.png" width="702" border="0" /></a></p>
<p>如上图所示知道：</p>
<p>==6118== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1 <br />==6118==&nbsp;&nbsp;&nbsp; at 0x4024F20: malloc (vg_replace_malloc.c:236) <br />==6118==&nbsp;&nbsp;&nbsp; by 0x8048724: GetMemory(char*, int) (in /home/netsky/workspace/a.out) <br />==6118==&nbsp;&nbsp;&nbsp; by 0x804874E: main (in /home/netsky/workspace/a.out) </p>
<p>是在main中调用了GetMemory导致的内存泄漏，GetMemory中是调用了malloc导致泄漏了100字节的内存。</p>
<blockquote>
<p><span style="font-size: small" size="3">Things to notice: <br />&#8226; There is a lot of information in each error message; read it carefully. <br />&#8226; The 6118 is the process ID; it&#8217;s usually unimportant. <br />&#8226; The ﬁrst line ("Heap Summary") tells you what kind of error it is. <br />&#8226; Below the ﬁrst line is a stack trace telling you where the problem occurred. Stack traces can get quite large, and be <br />confusing, especially if you are using the C++ STL. Reading them from the bottom up can help. </span></p>
<p><span style="font-size: small" size="3">&#8226; The code addresses (eg. 0x4024F20) are usually unimportant, but occasionally crucial for tracking down weirder <br />bugs.</span></p>
<p>The stack trace tells you where the leaked memory was allocated. Memcheck cannot tell you why the memory leaked, <br />unfortunately. (Ignore the "vg_replace_malloc.c", that&#8217;s an implementation detail.) <br />There are several kinds of leaks; the two most important categories are: <br />&#8226; "deﬁnitely lost": your program is leaking memory -- ﬁx it! <br />&#8226; "probably lost": your program is leaking memory, unless you&#8217;re doing funny things with pointers (such as moving <br />them to point to the middle of a heap block)</p></blockquote>
<p>Valgrind的使用请见手册<a href="http://valgrind.org/docs/manual/manual.html">http://valgrind.org/docs/manual/manual.html</a>。</p>
<h1>4、总结</h1>
<p>其实内存泄漏的原因可以概括为：调用了malloc/new等内存申请的操作，但缺少了对应的free/delete，总之就是，malloc/new比free/delete的数量多。我们在编程时需要注意这点，保证每个malloc都有对应的free，每个new都有对应的deleted！！！平时要养成这样一个好的习惯。</p>
<p>要避免内存泄漏可以总结为以下几点：</p>
<ul><li>程序员要养成良好习惯，保证malloc/new和free/delete匹配；</li><li>检测内存泄漏的关键原理就是，检查malloc/new和free/delete是否匹配，一些工具也就是这个原理。要做到这点，就是利用宏或者钩子，在用户程序与运行库之间加了一层，用于记录内存分配情况。 </li></ul>
<div id="MySignature">
<p><br /></p>
<div id="SkynetSignature">
<div><a href="http://www.cnblogs.com/skynet/" target="_blank"></a></div>
<p>作者：吴秦<br />出处：http://www.cnblogs.com/skynet/<br /></p></div></div><img src ="http://www.cppblog.com/yehao/aggbug/157967.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-10-10 14:59 <a href="http://www.cppblog.com/yehao/articles/157967.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言/C++中怎样产生随机数</title><link>http://www.cppblog.com/yehao/articles/156991.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Tue, 27 Sep 2011 11:11:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/156991.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/156991.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/156991.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/156991.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/156991.html</trackback:ping><description><![CDATA[<span class="Apple-style-span" style="word-spacing: 0px; font: medium Simsun; text-transform: none; color: rgb(0,0,0); text-indent: 0px; white-space: normal; letter-spacing: normal; border-collapse: separate; orphans: 2; widows: 2; webkit-border-horizontal-spacing: 0px; webkit-border-vertical-spacing: 0px; webkit-text-decorations-in-effect: none; webkit-text-size-adjust: auto; webkit-text-stroke-width: 0px"><span class="Apple-style-span" style="font-size: 14px; line-height: 21px; font-family: 'Microsoft Yahei', Tahoma, georgia; text-align: left"> 
<div class="asset-body" style="clear: both; padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em; padding-top: 0px; height: 293px">
<p dir="ltr" style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em; padding-top: 0px">C语言/C++怎样产生随机数：这里要用到的是rand()函数, srand()函数，C语言/C++里没有自带的random(int number)函数。<br />(1)&nbsp; 如果你只要产生随机数而不需要设定范围的话，你只要用rand()就可以了：rand()会返回一随机数值, 范围在0至RAND_MAX 间。RAND_MAX定义在stdlib.h, 其值为2147483647。<br />例如：</p>
<blockquote dir="ltr" style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em 30px; padding-top: 0px">
<p dir="ltr" style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em; padding-top: 0px">#include&lt;stdio.h&gt;<br />#include&lt;stdlib.h&gt;<br />void main()<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int i=0;i&lt;10;i+)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("%d\n",rand());<br />}</p></blockquote></div>
<div class="asset-more" id="more" style="clear: both; padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px; padding-top: 0px">
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em; padding-top: 0px">(2)&nbsp; 如果你要随机生成一个在一定范围的数，你可以在宏定义中定义一个random(int number)函数，然后在main()里面直接调用random()函数：</p>
<blockquote dir="ltr" style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em 30px; padding-top: 0px">
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em; padding-top: 0px">例如：随机生成10个0~100的数：<br />#include&lt;stdio.h&gt;<br />#include&lt;stdlib.h&gt;<br />#define random(x) (rand()%x)<br /><br />void main()<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp; for(int x=0;x&lt;10;x++)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("%d\n",random(100));<br />}</p></blockquote>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em; padding-top: 0px">&nbsp;</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em; padding-top: 0px">(3)但是上面两个例子所生成的随机数都只能是一次性的，如果你第二次运行的时候输出结果仍和第一次一样。这与srand()函数有关。srand()用来设置rand()产生随机数时的随机数种子。在调用rand()函数产生随机数前，必须先利用srand()设好随机数种子（seed）, 如果未设随机数种子, rand()在调用时会自动设随机数种子为1。上面的两个例子就是因为没有设置随机数种子，每次随机数种子都自动设成相同值1 ，进而导致rand()所产生的随机数值都一样。</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em; padding-top: 0px">srand()函数定义 ： void srand (unsigned int seed);<span class="Apple-converted-space">&nbsp;</span><br />通常可以利用geypid()或time(0)的返回值来当做seed<br />如果你用time(0)的话，要加入头文件#include&lt;time.h&gt;</p>
<blockquote dir="ltr" style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em 30px; padding-top: 0px">
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em; padding-top: 0px">例如：<br />#include&lt;stdio.h&gt;<br />#include&lt;stdlib.h&gt;<br />#include&lt;time.h&gt;<br />#define random(x) (rand()%x)<br /><br />void main()<br />{</p>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em; padding-top: 0px">&nbsp;&nbsp;&nbsp;&nbsp; srand((int)time(0));<br />&nbsp;&nbsp;&nbsp;&nbsp; for(int x=0;x&lt;10;x++)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("%d\n",random(100));<br />}</p></blockquote>
<p style="padding-right: 0px; padding-left: 0px; padding-bottom: 0px; margin: 0px 0px 0.75em; padding-top: 0px">这样两次运行的结果就会不一样了！！<br /><a href="http://www.ezloo.com/2008/03/cc_random.html">http://www.ezloo.com/2008/03/cc_random.html</a></p></div></span></span><img src ="http://www.cppblog.com/yehao/aggbug/156991.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-09-27 19:11 <a href="http://www.cppblog.com/yehao/articles/156991.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>c++程序链接的过程原理详解 </title><link>http://www.cppblog.com/yehao/articles/156876.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Mon, 26 Sep 2011 11:37:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/156876.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/156876.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/156876.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/156876.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/156876.html</trackback:ping><description><![CDATA[<p>许多Visual C++的使用者都碰到过LNK2005:symbol already defined和LNK1169:one or more multiply defined symbols found这样的链接错误，而且通常是在使用第三方库时遇到的。对于这个问题，有的朋友可能不知其然，而有的朋友可能知其然却不知其所以然，那么本文就试图为大家彻底解开关于它的种种疑惑。<br /><br />大家都知道，从C/C++源程序到可执行文件要经历两个阶段:(1)编译器将源文件编译成汇编代码，然后由汇编器(assembler)翻译成机器指令 (再加上其它相关信息)后输出到一个个目标文件(object file,VC的编译器编译出的目标文件默认的后缀名是.obj)中；(2)链接器(linker)将一个个的目标文件(或许还会有若干程序库)链接在一起生成一个完整的可执行文件。<br /><br />编译器编译源文件时会把源文件的全局符号(global symbol)分成强(strong)和弱(weak)两类传给汇编器，而随后汇编器则将强弱信息编码并保存在目标文件的符号表中。那么何谓强弱呢？编译器认为函数与初始化了的全局变量都是强符号，而未初始化的全局变量则成了弱符号。比如有这么个源文件:<br /></p>
<blockquote>extern int errorno;<br />int buf[2] = {1,2};<br />int *p;<br /><br />int main()<br />{<br />return 0;<br />}</blockquote>
<p><br /><br />其中main、buf是强符号，p是弱符号，而errorno则非强非弱，因为它只是个外部变量的使用声明。<br /><br />有了强弱符号的概念，我们就可以看看链接器是如何处理与选择被多次定义过的全局符号:<br /></p>
<blockquote>规则1: 不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号)；<br /><br /><br />规则2: 如果一个符号在某个目标文件中是强符号，在其它文件中都是弱符号，那么选择强符号；<br /><br /><br />规则3: 如果一个符号在所有目标文件中都是弱符号，那么选择其中任意一个；</blockquote>
<p><br /><br />由上可知多个目标文件不能重复定义同名的函数与初始化了的全局变量，否则必然导致LNK2005和LNK1169两种链接错误。可是，有的时候我们并没有在自己的程序中发现这样的重定义现象，却也遇到了此种链接错误，这又是何解？嗯，问题稍微有点儿复杂，容我慢慢道来。<br /><br />众所周知，ANSI C/C++ 定义了相当多的标准函数，而它们又分布在许多不同的目标文件中，如果直接以目标文件的形式提供给程序员使用的话，就需要他们确切地知道哪个函数存在于哪个目标文件中，并且在链接时显式地指定目标文件名才能成功地生成可执行文件，显然这是一个巨大的负担。所以C语言提供了一种将多个目标文件打包成一个文件的机制，这就是静态程序库(static library)。开发者在链接时只需指定程序库的文件名，链接器就会自动到程序库中寻找那些应用程序确实用到的目标模块，并把(且只把)它们从库中拷贝出来参与构建可执行文件。几乎所有的C/C++开发系统都会把标准函数打包成标准库提供给开发者使用(有不这么做的吗？)。<br /><br />程序库为开发者带来了方便，但同时也是某些混乱的根源。我们来看看链接器是如何解析(resolve)对程序库的引用的。<br /><br />在符号解析(symbol resolution)阶段，链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们，在此期间它要维护若干个集合:(1)集合E是将被合并到一起组成可执行文件的所有目标文件集合；(2)集合U是未解析符号(unresolved symbols，比如已经被引用但是还未被定义的符号)的集合；(3)集合D是所有之前已被加入到E的目标文件定义的符号集合。一开始，E、U、D都是空的。<br /></p>
<blockquote>(1): 对命令行中的每一个输入文件f，链接器确定它是目标文件还是库文件，如果它是目标文件，就把f加入到E，并把f中未解析的符号和已定义的符号分别加入到U、D集合中，然后处理下一个输入文件。<br /><br />(2): 如果f是一个库文件，链接器会尝试把U中的所有未解析符号与f中各目标模块定义的符号进行匹配。如果某个目标模块m定义了一个U中的未解析符号，那么就把 m加入到E中，并把m中未解析的符号和已定义的符号分别加入到U、D集合中。不断地对f中的所有目标模块重复这个过程直至到达一个不动点(fixed point)，此时U和D不再变化。而那些未加入到E中的f里的目标模块就被简单地丢弃，链接器继续处理下一输入文件。<br /><br />(3): 如果处理过程中往D加入一个已存在的符号，或者当扫描完所有输入文件时U非空，链接器报错并停止动作。否则，它把E中的所有目标文件合并在一起生成可执行文件。</blockquote>
<p><br /><br />VC带的编译器名字叫cl.exe，它有这么几个与标准程序库有关的选项: /ML、/MLd、/MT、/MTd、/MD、/MDd。这些选项告诉编译器应用程序想使用什么版本的C标准程序库。/ML(缺省选项)对应单线程静态版的标准程序库(libc.lib)；/MT对应多线程静态版标准库(libcmt.lib)，此时编译器会自动定义_MT宏；/MD对应多线程DLL版 (导入库msvcrt.lib，DLL是msvcrt.dll)，编译器自动定义_MT和_DLL两个宏。后面加d的选项都会让编译器自动多定义一个 _DEBUG宏，表示要使用对应标准库的调试版，因此/MLd对应调试版单线程静态标准库(libcd.lib)，/MTd对应调试版多线程静态标准库 (libcmtd.lib)，/MDd对应调试版多线程DLL标准库(导入库msvcrtd.lib，DLL是msvcrtd.dll)。虽然我们的确在编译时明白无误地告诉了编译器应用程序希望使用什么版本的标准库，可是当编译器干完了活，轮到链接器开工时它又如何得知一个个目标文件到底在思念谁？为了传递相思，我们的编译器就干了点秘密的勾当。在cl编译出的目标文件中会有一个专门的区域(关心这个区域到底在文件中什么地方的朋友可以参考COFF和 PE文件格式)存放一些指导链接器如何工作的信息，其中有一种就叫缺省库(default library)，这些信息指定了一个或多个库文件名，告诉链接器在扫描的时候也把它们加入到输入文件列表中(当然顺序位于在命令行中被指定的输入文件之后)。说到这里，我们先来做个小实验。写个顶顶简单的程序，然后保存为main.c :<br /></p>
<blockquote>/* main.c */<br />int main() { return 0; }</blockquote>
<p><br /><br />用下面这个命令编译main.c(什么？你从不用命令行来编译程序？这个......) :<br /></p>
<blockquote>cl /c main.c</blockquote>
<p><br /><br />/c 是告诉cl只编译源文件，不用链接。因为/ML是缺省选项，所以上述命令也相当于: cl /c /ML main.c 。如果没什么问题的话(要出了问题才是活见鬼！当然除非你的环境变量没有设置好，这时你应该去VC的bin目录下找到vcvars32.bat文件然后运行它。)，当前目录下会出现一个main.obj文件，这就是我们可爱的目标文件。随便用一个文本编辑器打开它(是的，文本编辑器，大胆地去做别害怕)，搜索"defaultlib"字符串，通常你就会看到这样的东西: "-defaultlib:LIBC -defaultlib:OLDNAMES"。啊哈，没错，这就<br />是保存在目标文件中的缺省库信息。我们的目标文件显然指定了两个缺省库，一个是单线程静态版标准库libc.lib(这与/ML选项相符)，另外一个是oldnames.lib(它是为了兼容微软以前的C/C++开发系统)。<br /><br />VC的链接器是link.exe，因为main.obj保存了缺省库信息，所以可以用<br /></p>
<blockquote>link main.obj libc.lib</blockquote>
<p><br /><br />或者<br /></p>
<blockquote>link main.obj</blockquote>
<p><br /><br />来生成可执行文件main.exe，这两个命令是等价的。但是如果你用<br /></p>
<blockquote>link main.obj libcd.lib</blockquote>
<p><br /><br />的话，链接器会给出一个警告: "warning LNK4098: defaultlib "LIBC" conflicts with use of other libs; use /NODEFAULTLIB:library"，因为你显式指定的标准库版本与目标文件的缺省值不一致。通常来说，应该保证链接器合并的所有目标文件指定的缺省标准库版本一致，否则编译器一定会给出上面的警告，而LNK2005和LNK1169链接错误则有时会出现有时不会。那么这个有时到底是什么时候？呵呵，别着急，下面的一切正是为喜欢追根究底的你准备的。<br /><br />建一个源文件，就叫mylib.c，内容如下:<br /></p>
<blockquote>/* mylib.c */<br />＃i nclude<br /><br />void foo()<br />{<br />printf("%s","I am from mylib!/n");<br />}</blockquote>
<p><br /><br />用<br /></p>
<blockquote>cl /c /MLd mylib.c</blockquote>
<p><br /><br />命令编译，注意/MLd选项是指定libcd.lib为默认标准库。lib.exe是VC自带的用于将目标文件打包成程序库的命令，所以我们可以用<br /></p>
<blockquote>lib /OUT:my.lib mylib.obj</blockquote>
<p><br /><br />将mylib.obj打包成库，输出的库文件名是my.lib。接下来把main.c改成:<br /></p>
<blockquote>/* main.c */<br />void foo();<br /><br />int main()<br />{<br />foo();<br />return 0;<br />}</blockquote>
<p><br /><br />用<br /></p>
<blockquote>cl /c main.c</blockquote>
<p><br /><br />编译，然后用<br /></p>
<blockquote>link main.obj my.lib</blockquote>
<p><br /><br />进行链接。这个命令能够成功地生成main.exe而不会产生LNK2005和LNK1169 链接错误，你仅仅是得到了一条警告信息:"warning LNK4098: defaultlib "LIBCD" conflicts with use of other libs; use /NODEFAULTLIB:library"。我们根据前文所述的扫描规则来分析一下链接器此时做了些啥。<br /><br />一开始E、U、D都是空集，链接器首先扫描到main.obj，把它加入E集合，同时把未解析的foo加入U，把main加入D，而且因为 main.obj的默认标准库是libc.lib，所以它被加入到当前输入文件列表的末尾。接着扫描my.lib，因为这是个库，所以会拿当前U中的所有符号(当然现在就一个foo)与my.lib中的所有目标模块(当然也只有一个mylib.obj)依次匹配，看是否有模块定义了U中的符号。结果 mylib.obj确实定义了foo，于是它被加入到E，foo从U转移到D，mylib.obj引用的printf加入到U，同样地， mylib.obj指定的默认标准库是libcd.lib，它也被加到当前输入文件列表的末尾(在libc.lib的后面)。不断地在my.lib库的各模块上进行迭代以匹配U中的符号，直到U、D都不再变化。很明显，现在就已经到达了这么一个不动点，所以接着扫描下一个输入文件，就是libc.lib。链接器发现libc.lib里的printf.obj里定义有printf，于是printf从U移到D，而printf.obj被加入到E，它定义的所有符号加入到D，它里头的未解析符号加入到U。链接器还会把每个程序都要用到的一些初始化操作所在的目标模块(比如crt0.obj等)及它们所引用的模块(比如malloc.obj、free.obj等)自动加入到E中，并更新U和D以反应这个变化。事实上，标准库各目标模块里的未解析符号都可以在库内其它模块中找到定义，因此当链接器处理完libc.lib时，U一定是空的。最后处理libcd.lib，因为此时U已经为空，所以链接器会抛弃它里面的所有目标模块从而结束扫描，然后合并E中的目标模块并输出可执行文件。<br /><br />上文描述了虽然各目标模块指定了不同版本的缺省标准库但仍然链接成功的例子，接下来你将目睹因为这种不严谨而导致的悲惨失败。<br /><br />修改mylib.c成这个样子:<br /></p>
<blockquote>＃include<br /><br />void foo()<br />{<br />// just a test , don"t care memory leak<br />_malloc_dbg( 1, _NORMAL_BLOCK, __FILE__, __LINE__ );<br />}<br /></blockquote>
<p><br />其中_malloc_dbg不是ANSI C的标准库函数，它是VC标准库提供的malloc的调试版，与相关函数配套能帮助开发者抓各种内存错误。使用它一定要定义_DEBUG宏，否则预处理器会把它自动转为malloc。继续用<br /></p>
<blockquote>cl /c /MLd mylib.c<br />lib /OUT:my.lib mylib.obj</blockquote>
<p><br /><br />编译打包。当再次用<br /></p>
<blockquote>link main.obj my.lib</blockquote>
<p><br /><br />进行链接时，我们看到了什么？天哪，一堆的LNK2005加上个贵为"fatal error"的LNK1169垫底，当然还少不了那个LNK4098。链接器是不是疯了？不，你冤枉可怜的链接器了，我拍胸脯保证它可是一直在尽心尽责地照章办事。<br /><br />一开始E、U、D为空，链接器扫描main.obj，把它加入E，把foo加入U，把main加入D，把libc.lib加入到当前输入文件列表的末尾。接着扫描my.lib，foo从U转移到D，_malloc_dbg加入到U，libcd.lib加到当前输入文件列表的尾部。然后扫描 libc.lib，这时会发现libc.lib里任何一个目标模块都没有定义_malloc_dbg(它只在调试版的标准库中存在)，所以不会有任何一个模块因为_malloc_dbg而加入E，但是每个程序都要用到的初始化模块(如crt0.obj等)及它们所引用的模块(比如malloc.obj、 free.obj等)还是会自动加入到E中，同时U和D被更新以反应这个变化。当链接器处理完libc.lib时，U只剩_malloc_dbg这一个符号。最后处理libcd.lib，发现dbgheap.obj定义了_malloc_dbg，于是dbgheap.obj加入到E，它里头的未解析符号加入U，它定义的所有其它符号也加入D，这时灾难便来了。之前malloc等符号已经在D中(随着libc.lib里的malloc.obj加入E而加入的)，而dbgheap.obj又定义了包括malloc在内的许多同名符号，这引发了重定义冲突，链接器只好中断工作并报告错误。<br /><br />现在我们该知道，链接器完全没有责任，责任在我们自己的身上。是我们粗心地把缺省标准库版本不一致的目标文件(main.obj)与程序库 (my.lib)链接起来，导致了大灾难。解决办法很简单，要么用/MLd选项来重编译main.c；要么用/ML选项重编译mylib.c。<br /><br />在上述例子中，我们拥有库my.lib的源代码(mylib.c)，所以可以用不同的选项重新编译这些源代码并再次打包。可如果使用的是第三方的库，它并没有提供源代码，那么我们就只有改变自己程序的编译选项来适应这些库了。但是如何知道库中目标模块指定的默认库呢？其实VC提供的一个小工具便可以完成任务，这就是dumpbin.exe。运行下面这个命令<br /></p>
<blockquote>dumpbin /DIRECTIVES my.lib</blockquote>
<p><br /><br />然后在输出中找那些"Linker Directives"引导的信息，你一定会发现每一处这样的信息都会包含若干个类似"-defaultlib:XXXX"这样的字符串，其中XXXX便代表目标模块指定的缺省库名。<br /><br />知道了第三方库指定的默认标准库，再用合适的选项编译我们的应用程序，就可以避免LNK2005和LNK1169 链接错误。喜欢IDE的朋友，你一样可以到 "Project属性" -&gt; "C/C++" -&gt; "代码生成(code generation)" -&gt; "运行时库(run-time library)" 项下设置应用程序的默认标准库版本，这与命令行选项的效果是一样的。<br /><br />终极解决办法：<br /></p>
<blockquote>在 Project/Setting/Link/General中的 Project Options: 加入 <span style="color: #ff0000">/FORCE:MULTIPLE</span>即可。</blockquote><a href="http://blog.csdn.net/czg1984/article/details/4658749">http://blog.csdn.net/czg1984/article/details/4658749</a><img src ="http://www.cppblog.com/yehao/aggbug/156876.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-09-26 19:37 <a href="http://www.cppblog.com/yehao/articles/156876.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>__declspec(selectany)的作用</title><link>http://www.cppblog.com/yehao/articles/149384.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Fri, 24 Jun 2011 06:49:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/149384.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/149384.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/149384.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/149384.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/149384.html</trackback:ping><description><![CDATA[<div class="cnt" id="blog_text">
<p>转自<font face="Verdana" color="#000000">http://hi.baidu.com/shitiansunny/blog/item/8de3368761204f2a67096e1c.html</font><br /><br />最近在用 template 编写singleton模式代码的时候，遇到了一个问题，template要求实现要在同一个文件中，所以，我只能在h文件中定义并实现 singleton 模式类。类中必然要有静态成员变量，静态成员变量的定义成了问题，如果我放在cpp文件中，模板是不支持的，放在h文件中，如果h文件被多次包含，会出现重定义的情况。</p>
<p>&nbsp;&nbsp;&nbsp; 回来，请教高手，得知，可以在初始化静态成员变量前面加上__declspec(selectany) ，这样编译器会自动剔除对该静态成员的重复定义。</p>
<p>&nbsp;&nbsp; 最近半年也一直用WTL，ATL，COM等。其实在WTL，ATL中已经大量使用了__declspec(selectany)方法。我猜想这是为解决template单文件编程和静态成员变量在头文件中定义会出现重复定义矛盾而提出的。</p>
<p>总的来说：</p>
<p>&nbsp;&nbsp;&nbsp; __declspec(selelctany) 使在头文件中定义静态成员变量可行。</p>
<p>===================================================================================</p>
<p>&nbsp;</p>
<p>其他资料：</p>
<p>selectany使用在c/c++工程的连接期间，一般用得很少，所以很陌生。</p>
<p>这个属性告诉编译器声明的全局变量是一个"任一拣选"(pick-any)COMDAT.在连接时间，如果多个COMDAT定义能看到，连接器选择一个并且丢弃所有的剩余的。如果连接器选项/OPT:REF被选择，COMDAT中所有的没有引用的数据项被删除。</p>
<p>一个全局数据在EXE或者DLL中只能被初始化一次。当同一个头文件被多个源文件引用时，在头中定义全局数据始始化时，这个属性被使用。这个属性在c和c++的编译器中都是可用的。</p>
<p><br />COMDAT record<br />一个常用对象文件格式(COFF)记录,它包含的已被初始化的常用块数据和打包的函数对连接器是可以见的。<br />packaged function<br />当函数级的连接功能选择开关被打开时，一个函数能被编译器创建。在编译器产生的对象文件中COMDAT记录的打包的函数对于连接器是可见的。没有打包的函数只在对象级(the object level)上连接。</p>
<p>下面是MSDN上一些例子:</p>
<p style="text-indent: 2em">//Correct - x1 is initialized and externally visible</p>
<p style="text-indent: 2em">__declspec(selectany) int x1=1;</p>
<p style="text-indent: 2em">//Incorrect - const is by default static in C++, so</p>
<p style="text-indent: 2em">//x2 is not visible externally (This is OK in C, since</p>
<p style="text-indent: 2em">//const is not by default static in C)</p>
<p style="text-indent: 2em">const __declspec(selectany) int x2 =2;</p>
<p style="text-indent: 2em">//Correct - x3 is extern const, so externally visible</p>
<p style="text-indent: 2em">extern const __declspec(selectany) int x3=3;</p>
<p style="text-indent: 2em">//Correct - x4 is extern const, so it is externally visible</p>
<p style="text-indent: 2em">extern const int x4;</p>
<p style="text-indent: 2em">const __declspec(selectany) int x4=4;</p>
<p style="text-indent: 2em">//Incorrect - __declspec(selectany) is applied to the uninitialized</p>
<p style="text-indent: 2em">//declaration of x5</p>
<p style="text-indent: 2em">extern __declspec(selectany) int x5;</p></div><br /><img src ="http://www.cppblog.com/yehao/aggbug/149384.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-06-24 14:49 <a href="http://www.cppblog.com/yehao/articles/149384.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>函数的调用规则(__cdecl,__stdcall,__fastcall,__pascal)</title><link>http://www.cppblog.com/yehao/articles/146428.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Sun, 15 May 2011 13:59:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/146428.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/146428.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/146428.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/146428.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/146428.html</trackback:ping><description><![CDATA[<p>关于函数的调用规则（调用约定），大多数时候是不需要了解的，但是如果需要跨语言的编程，比如VC写的dll要delphi调用，则需要了解。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; microsoft的vc默认的是__cdecl方式，而windows API则是__stdcall，如果用vc开发dll给其他语言用，则应该指定__stdcall方式。堆栈由谁清除这个很重要，如果是要写汇编函数给C调用，一定要小心堆栈的清除工作，如果是__cdecl方式的函数，则函数本身（如果不用汇编写）则不需要关心保存参数的堆栈的清除，但是如果是__stdcall的规则，一定要在函数退出(ret)前恢复堆栈。<br>1.__cdecl<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 所谓的C调用规则。按从右至左的顺序压参数入栈，由调用者把参数弹出栈。切记：对于传送参数的内存栈是由调用者来维护的。返回值在EAX中因此，对于象printf这样变参数的函数必须用这种规则。编译器在编译的时候对这种调用规则的函数生成修饰名的饿时候，仅在输出函数名前加上一个下划线前缀，格式为_functionname。 <br>2.__stdcall <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 按从右至左的顺序压参数入栈，由被调用者把参数弹出栈。_stdcall是Pascal程序的缺省调用方式，通常用于Win32 Api中，切记：函数自己在退出时清空堆栈，返回值在EAX中。　　__stdcall调用约定在输出函数名前加上一个下划线前缀，后面加上一个&#8220;@&#8221;符号和其参数的字节数，格式为<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#102;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#110;&#97;&#109;&#101;&#64;&#110;&#117;&#109;&#98;&#101;&#114;">_functionname@number</a>。如函数int func(int a, double b)的修饰名是<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#102;&#117;&#110;&#99;&#64;&#49;&#50;">_func@12</a>。<br>3.__fastcall<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __fastcall调用的主要特点就是快，因为它是通过寄存器来传送参数的（实际上，它用ECX和EDX传送前两个双字（DWORD）或更小的参数，剩下的参数仍旧自右向左压栈传送，被调用的函数在返回前清理传送参数的内存栈）。__fastcall调用约定在输出函数名前加上一个&#8220;@&#8221;符号，后面也是一个&#8220;@&#8221;符号和其参数的字节数，格式为@functionname@number。这个和__stdcall很象，唯一差别就是头两个参数通过寄存器传送。注意通过寄存器传送的两个参数是从左向右的，即第一个参数进ECX，第2个进EDX，其他参数是从右向左的入stack。返回仍然通过EAX.<br>4.__pascal<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这种规则从左向右传递参数，通过EAX返回，堆栈由被调用者清除</p>
<p>&nbsp;</p>
<p>5.__thiscall</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 仅仅应用于"C++"成员函数。this指针存放于CX寄存器，参数从右到左压。thiscall不是关键词，因此不能被程序员指定</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 调用约定可以通过工程设置：Setting...\C/C++ \Code Generation项进行选择，缺省状态为__cdecl。</p>
<p>&nbsp;</p>
<p>名字修饰约定：</p>
<p>1、修饰名(Decoration name)："C"或者"C++"函数在内部（编译和链接）通过修饰名识别<br>2、C编译时函数名修饰约定规则：<br>__stdcall调用约定在输出函数名前加上一个下划线前缀，后面加上一个"@"符号和其参数的字节数，格式为<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#102;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#110;&#97;&#109;&#101;&#64;&#110;&#117;&#109;&#98;&#101;&#114;">_functionname@number</a>,例如 ：function(int a, int b)，其修饰名为：<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#102;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#64;&#56;">_function@8</a><br>__cdecl调用约定仅在输出函数名前加上一个下划线前缀，格式为_functionname。<br>__fastcall调用约定在输出函数名前加上一个"@"符号，后面也是一个"@"符号和其参数的字节数，格式为@functionname@number。</p>
<p>3、C++编译时函数名修饰约定规则：<br>__stdcall调用约定：<br>1)、以"?"标识函数名的开始，后跟函数名；<br>2)、函数名后面以"@@YG"标识参数表的开始，后跟参数表；<br>3)、参数表以代号表示：<br>X--void ，<br>D--char，<br>E--unsigned char，<br>F--short，<br>H--int，<br>I--unsigned int，<br>J--long，<br>K--unsigned long，<br>M--float，<br>N--double，<br>_N--bool，<br>PA--表示指针，后面的代号表明指针类型，如果相同类型的指针连续出现，以"0"代替，一个"0"代表一次重复；<br>4)、参数表的第一项为该函数的返回值类型，其后依次为参数的数据类型,指针标识在其所指数据类型前； <br>5)、参数表后以"@Z"标识整个名字的结束，如果该函数无参数，则以"Z"标识结束。<br>其格式为"<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#63;&#102;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#110;&#97;&#109;&#101;&#64;&#64;&#89;&#71;&#42;&#42;&#42;&#42;&#42;&#64;&#90;">?functionname@@YG*****@Z</a>"或"<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#63;&#102;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#110;&#97;&#109;&#101;&#64;&#64;&#89;&#71;&#42;&#88;&#90;">?functionname@@YG*XZ</a>"，例如<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int Test1(char *var1,unsigned long)-----&#8220;?Test1@@YGHPADK@Z&#8221;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void Test2()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#45;&#45;&#45;&#45;&#45;&#8220;&#63;&#84;&#101;&#115;&#116;&#50;&#64;&#64;&#89;&#71;&#88;&#88;&#90;">-----&#8220;?Test2@@YGXXZ</a>&#8221;</p>
<p>&nbsp;</p>
<p>__cdecl调用约定：<br>规则同上面的_stdcall调用约定，只是参数表的开始标识由上面的"@@YG"变为"@@YA"。<br>__fastcall调用约定：<br>规则同上面的_stdcall调用约定，只是参数表的开始标识由上面的"@@YG"变为"@@YI"。<br>VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用. </p>
<p><br>注意：<br>1、_beginthread需要__cdecl的线程函数地址，_beginthreadex和CreateThread需要__stdcall的线程函数地址。</p>
<p>2、一般WIN32的函数都是__stdcall。而且在Windef.h中有如下的定义：<br>#define CALLBACK __stdcall<br>#define WINAPI　 __stdcall</p>
<p>3、extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);<br>&nbsp;&nbsp; typedef int (__cdecl*FunPointer)(int a, int b);<br>&nbsp;&nbsp; 修饰符的书写顺序如上。</p>
<p>4、extern "C"的作用：如果Add(int a, int b)是在c语言编译器编译，而在c++文件使用，则需要在c++文件中声明：extern "C" Add(int a, int b)，因为c编译器和c++编译器对函数名的解释不一样（c++编译器解释函数名的时候要考虑函数参数，这样是了方便函数重载，而在c语言中不存在函数重载的问题），使用extern "C"，实质就是告诉c++编译器，该函数是c库里面的函数。如果不使用extern "C"则会出现链接错误。<br>一般象如下使用：<br>#ifdef _cplusplus <br>#define ETERN_C extern "C"<br>#else<br>#define EXTERN_C extern<br>#endif</p>
<p>#ifdef _cplusplus <br>extern "C"{<br>#endif <br>EXTERN_C int func(int a, int b); <br>#ifdef _cplusplus <br>} <br>#endif</p>
<p>5、MFC提供了一些宏，可以使用AFX_EXT_CLASS来代替__declspec(DLLexport)，并修饰类名，从而导出类，AFX_API_EXPORT来修饰函数，AFX_DATA_EXPORT来修饰变量<br>AFX_CLASS_IMPORT：__declspec(DLLexport)<br>AFX_API_IMPORT：__declspec(DLLexport)<br>AFX_DATA_IMPORT：__declspec(DLLexport)<br>AFX_CLASS_EXPORT：__declspec(DLLexport)<br>AFX_API_EXPORT：__declspec(DLLexport)<br>AFX_DATA_EXPORT：__declspec(DLLexport)<br>AFX_EXT_CLASS：#ifdef _AFXEXT <br>&nbsp;&nbsp;&nbsp; AFX_CLASS_EXPORT<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #else<br>&nbsp;&nbsp;&nbsp; AFX_CLASS_IMPORT</p>
<p>6、DLLMain负责初始化(Initialization)和结束(Termination)工作，每当一个新的进程或者该进程的新的线程访问DLL时，或者访问DLL的每一个进程或者线程不再使用DLL或者结束时，都会调用DLLMain。但是，使用TerminateProcess或TerminateThread结束进程或者线程，不会调用DLLMain。</p>
<p>7、一个DLL在内存中只有一个实例<br>DLL程序和调用其输出函数的程序的关系：<br>1)、DLL与进程、线程之间的关系<br>DLL模块被映射到调用它的进程的虚拟地址空间。<br>DLL使用的内存从调用进程的虚拟地址空间分配，只能被该进程的线程所访问。<br>DLL的句柄可以被调用进程使用；调用进程的句柄可以被DLL使用。<br>DLLDLL可以有自己的数据段，但没有自己的堆栈，使用调用进程的栈，与调用它的应用程序相同的堆栈模式。</p>
<p>2)、关于共享数据段<br>DLL定义的全局变量可以被调用进程访问；DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量，则需要使用同步机制；对一个DLL的变量，如果希望每个使用DLL的线程都有自己的值，则应该使用线程局部存储(TLS，Thread Local Strorage)。</p>
<p>&nbsp;</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/jia_xiaoxin/archive/2008/09/02/2868216.aspx">http://blog.csdn.net/jia_xiaoxin/archive/2008/09/02/2868216.aspx</a></p>
<img src ="http://www.cppblog.com/yehao/aggbug/146428.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-15 21:59 <a href="http://www.cppblog.com/yehao/articles/146428.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++编程对缓冲区的理解</title><link>http://www.cppblog.com/yehao/articles/146416.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Sun, 15 May 2011 08:52:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/146416.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/146416.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/146416.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/146416.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/146416.html</trackback:ping><description><![CDATA[<p>转自<a href="http://www.vckbase.com/document/viewdoc/?id=1897">http://www.vckbase.com/document/viewdoc/?id=1897</a><br><span style="COLOR: #ff0000">Part of the red marker to add by YeHao，Order oneself to better understand.<br></span><br><strong><br>什么是缓冲区</strong><br>缓冲区又称为缓存，它是内存空间的一部分。也就是说，在内存空间中预留了一定的存储空间，这些存储空间用来缓冲输入或输出的数据，这部分预留的空间就叫做缓冲区。<br>缓冲区根据其对应的是输入设备还是输出设备，分为输入缓冲区和输出缓冲区。<br><br><strong>为什么要引入缓冲区</strong><br>我们为什么要引入缓冲区呢？<br>比如我们从磁盘里取信息，我们先把读出的数据放在缓冲区，计算机再直接从缓冲区中取数据，等缓冲区的数据取完后再去磁盘中读取，这样就可以减少磁盘的读写次数，再加上计算机对缓冲区的操作大大快于对磁盘的操作，故应用缓冲区可大大提高计算机的运行速度。<br>又比如，我们使用打印机打印文档，由于打印机的打印速度相对较慢，我们先把文档输出到打印机相应的缓冲区，打印机再自行逐步打印，这时我们的CPU可以处理别的事情。<br>现在您基本明白了吧，缓冲区就是一块内存区，它用在输入输出设备和CPU之间，用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作，避免低速的输入输出设备占用CPU，解放出CPU，使其能够高效率工作。<br><br><strong>缓冲区的类型</strong><br>缓冲区 分为三种类型：全缓冲、行缓冲和不带缓冲。<br>1、全缓冲<br>在这种情况下，当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。<br>2、行缓冲<br>在这种情况下，当在输入和输出中遇到换行符时，执行真正的I/O操作。这时，我们输入的字符先存放在缓冲区，等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。<br>3、不带缓冲<br>也就是不进行缓冲，标准出错情况stderr是典型代表，这使得出错信息可以直接尽快地显示出来。<br>缓冲区的刷新<br>下列情况会引发缓冲区的刷新:<br>1、缓冲区满时；<br>2、执行flush语句；<br>3、执行endl语句；<br>4、关闭文件。<br>可见，缓冲区满或关闭文件时都会刷新缓冲区，进行真正的I/O操作。另外，在C++中，我们可以使用flush函数来刷新缓冲区（执行I/O操作并清空缓冲区），如：<br>cout&lt;&lt;flush; //将显存的内容立即输出到显示器上进行显示</p>
<p>endl控制符的作用是将光标移动到输出设备中下一行开头处，并且清空缓冲区。<br>cout&lt;&lt;endl;<br>相当于<br>cout&lt;&lt;&#8221;\n&#8221; &lt;&lt;flush;</p>
<p><strong>通过实例演示说明</strong></p>
<p><strong>1、文件操作演示全缓冲</strong><br>创建一个控制台工程，输入如下代码： </p>
<pre>#include &lt;fstream&gt;
using namespace std;
int main()
{
//创建文件test.txt并打开
ofstream outfile("test.txt");
//向test.txt文件中写入4096个字符&#8217;a&#8217;
for(int n=0;n&lt;4096;n++)<span style="COLOR: red">//经本人用VC6.0测试，作者说的是对的，但是在Dev-C++编译器中测试发现，将4096改为3000也会直接写入文件</span>
{
outfile&lt;&lt;'a';
}
//暂停，按任意键继续
system("PAUSE");
//继续向test.txt文件中写入字符&#8217;b&#8217;，也就是说，第4097个字符是&#8217;b&#8217;
outfile&lt;&lt;'b';
//暂停，按任意键继续
system("PAUSE");
return 0;
}</pre>
<p>上面这段代码很容易理解，已经在代码内部作了注释。<br>编写这段小代码的目的是验证WindowsXP下全缓冲的大小是4096个字节，并验证缓冲区满后会刷新缓冲区，执行真正的I/O操作。</p>
<p>编译并执行，运行结果如下：<br><img src="http://www.vckbase.com/document/journal/vckbase55/images/buf002.jpg" width=268 height=213><br>此时打开工程所在文件夹下的test.txt文件，您会发现该文件是空的，这说明4096个字符&#8220;a&#8221;还在缓冲区，并没有真正执行I/O操作。敲一下回车键，窗口变为如下：<br><img src="http://www.vckbase.com/document/journal/vckbase55/images/buf004.jpg" width=268 height=213><br>此时再打开test.txt文件，您就会发下该文件中已经有了4096个字符&#8220;a&#8221;。这说明全缓冲区的大小是4K（4096），缓冲区满后执行了I/O操作，而字符&#8220;b&#8221;还在缓冲区。<br>再次敲一下回车键，窗口变为如下：<br><img src="http://www.vckbase.com/document/journal/vckbase55/images/buf006.jpg" width=266 height=212><br>此时再打开test.txt文件，您就会发现字符&#8220;b&#8221;也在其中了。这一步验证了文件关闭时刷新了缓冲区。</p>
<p><strong>2、键盘操作演示行缓冲</strong><br>先介绍getchar()函数。<br>函数原型：int getchar(void);<br>说明：当程序调用getchar()函数时，程序就等着用户按键，用户输入的字符被存放在键盘缓冲区中，直到用户按回车为止（回车字符也放在缓冲区中）。当用户键入回车之后，getchar()函数才开始从键盘缓冲区中每次读入一个字符。也就是说，后续的getchar()函数调用不会等待用户按键，而直接读取缓冲区中的字符，直到缓冲区中的字符读完后，才重新等待用户按键。<br>不知道您明白了没有，再通俗一点讲，当程序调用getchar()函数时，程序就等着用户按键，并等用户按下回车键返回。期间按下的字符存放在缓冲区，第一个字符作为函数返回值。继续调用getchar()函数，将不再等用户按键，而是返回您刚才输入的第2个字符；继续调用，返回第3个字符，直到缓冲区中的字符读完后，才等待用户按键。<br>如果您还没有明白，只能怨我表达能力有限，您可以结合以下实例体会。</p>
<p>创建一个控制台工程，输入如下代码：</p>
<pre>#include &lt;iostream&gt;
using namespace std;
int main()
{
char c;
//第一次调用getchar()函数
//程序执行时，您可以输入一串字符并按下回车键，按下回车键后该函数才返回
c=getchar();
//显示getchar()函数的返回值
cout&lt;&lt;c&lt;&lt;endl;
//暂停
system("PAUSE");
//循环多次调用getchar()函数
//将每次调用getchar()函数的返回值显示出来
//直到遇到回车符才结束
while((c=getchar())!='\n')
{
printf("%c",c);
}
//暂停
system("PAUSE");
return 0;
}</pre>
<p>这段小代码也很简单，同样在代码内部都有注释。<br>getchar()函数的执行就是采用了行缓冲。第一次调用getchar()函数，会让程序使用者（用户）输入一行字符并直至按下回车键 函数才返回。此时用户输入的字符和回车符都存放在行缓冲区。<br>再次调用getchar()函数，会逐步输出行缓冲区的内容。<br>好了，本人表达能力有限，还是编译运行程序，通过运行结果自己领会吧。</p>
<p>编译运行程序，会提示您输入字符，您可以交替按下一些字符，如下：<br><img src="http://www.vckbase.com/document/journal/vckbase55/images/buf008.jpg" width=553 height=361> <br>您一直按下去，您就会发现当您按到第4094个字符时，不允许您继续输入字符。这说明行缓冲区的大小也是4K。<br>此时您按下回车键，返回第一个字符&#8217;a&#8217;，如下图：<br><img src="http://www.vckbase.com/document/journal/vckbase55/images/buf010.jpg" width=554 height=361><br>继续敲一下回车键，将缓冲区的其它的字符全部输出，如下图：<br><img src="http://www.vckbase.com/document/journal/vckbase55/images/buf012.jpg" width=554 height=361></p>
<p><strong>3、标准错误输出不带缓冲</strong><br>如错误输出时使用：</p>
<p>cerr&lt;&lt;&#8221;错误，请检查输入的参数!&#8221;;</p>
<p>这条语句等效于：<br>fprintf(stderr, &#8221;错误，请检查输入的参数!&#8221;);<br></p>
<p>好了，就说到这吧，祝您好运，希望能对您有所帮助。</p>
<br>
<img src ="http://www.cppblog.com/yehao/aggbug/146416.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-15 16:52 <a href="http://www.cppblog.com/yehao/articles/146416.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在VC中使用安全版字符串操作函数</title><link>http://www.cppblog.com/yehao/articles/146266.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Thu, 12 May 2011 10:19:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/146266.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/146266.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/146266.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/146266.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/146266.html</trackback:ping><description><![CDATA[<a href="http://blog.kingsamchen.com/archives/539">http://blog.kingsamchen.com/archives/539</a>
<p>&nbsp;</p>
<img src ="http://www.cppblog.com/yehao/aggbug/146266.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-12 18:19 <a href="http://www.cppblog.com/yehao/articles/146266.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++堆、栈、自由存储区、全局/静态存储区和常量存储区（二）</title><link>http://www.cppblog.com/yehao/articles/145814.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Fri, 06 May 2011 05:19:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/145814.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/145814.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/145814.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/145814.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/145814.html</trackback:ping><description><![CDATA[在C++中，内存分成5个区，他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。<br>&nbsp;&nbsp;<font color=#ee82ee>&nbsp; 栈，就是那些由编译器在需要的时候分配，在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。<br></font>&nbsp;&nbsp;&nbsp; <font color=#006400>堆，就是那些由new分配的内存块，他们的释放编译器不去管，由我们的应用程序去控制，一般一个new就要对应一个delete。如果程序员没有释放掉，那么在程序结束后，操作系统会自动回收。<br></font>&nbsp;&nbsp;&nbsp; 自由存储区，就是那些由malloc等分配的内存块，他和堆是十分相似的，不过它是用free来结束自己的生命的。<br>&nbsp;&nbsp;&nbsp; 全局/静态存储区，全局变量和静态变量被分配到同一块内存中，在以前的C语言中，全局变量又分为初始化的和未初始化的，在C++里面没有这个区分了，他们共同占用同一块内存区。<br>&nbsp;&nbsp;&nbsp; 常量存储区，这是一块比较特殊的存储区，他们里面存放的是常量，不允许修改（当然，你要通过非正当手段也可以修改，而且方法很多）<br><strong>明确区分堆与栈<br></strong>&nbsp;&nbsp;&nbsp; 在bbs上，堆与栈的区分问题，似乎是一个永恒的话题，由此可见，初学者对此往往是混淆不清的，所以我决定拿他第一个开刀。<br>&nbsp;&nbsp;&nbsp; 首先，我们举一个例子：<br>&nbsp;&nbsp;&nbsp; void f() { int* p=new int[5]; } <br>&nbsp;&nbsp;&nbsp; 这条短短的一句话就包含了堆与栈，看到new，我们首先就应该想到，我们分配了一块堆内存，那么指针p呢？他分配的是一块栈内存，所以这句话的意思就是： 在栈内存中存放了一个指向一块堆内存的指针p。在程序会先确定在堆中分配内存的大小，然后调用operator new分配内存，然后返回这块内存的首地址，放入栈中，他在VC6下的汇编代码如下：<br>&nbsp;&nbsp;&nbsp; 00401028&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 14h<br>&nbsp;&nbsp;&nbsp; 0040102A&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; operator new (00401060)<br>&nbsp;&nbsp;&nbsp; 0040102F&nbsp;&nbsp; add&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; esp,4<br>&nbsp;&nbsp;&nbsp; 00401032&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dword ptr [ebp-8],eax<br>&nbsp;&nbsp;&nbsp; 00401035&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; eax,dword ptr [ebp-8]<br>&nbsp;&nbsp;&nbsp; 00401038&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dword ptr [ebp-4],eax<br>&nbsp;&nbsp;&nbsp; 这里，我们为了简单并没有释放内存，那么该怎么去释放呢？是delete p么？澳，错了，应该是delete []p，这是为了告诉编译器：我删除的是一个数组，VC6就会根据相应的Cookie信息去进行释放内存的工作。<br>&nbsp;&nbsp;&nbsp; 好了，我们回到我们的主题：堆和栈究竟有什么区别？ <br>&nbsp;&nbsp;&nbsp; 主要的区别由以下几点：<br>&nbsp;&nbsp;&nbsp; 1、管理方式不同；<br>&nbsp;&nbsp;&nbsp; 2、空间大小不同；<br>&nbsp;&nbsp;&nbsp; 3、能否产生碎片不同；<br>&nbsp;&nbsp;&nbsp; 4、生长方向不同；<br>&nbsp;&nbsp;&nbsp; 5、分配方式不同；<br>&nbsp;&nbsp;&nbsp; 6、分配效率不同；<br>&nbsp;&nbsp;&nbsp; 管理方式：对于栈来讲，是由编译器自动管理，无需我们手工控制；对于堆来说，释放工作由程序员控制，容易产生memory leak。空间大小：一般来讲在32位系统下，堆内存可以达到4G的空间，从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲，一般都是有一定的空间大小的，例如，在VC6下面，默认的栈空间大小是1M（好像是，记不清楚了）。当然，我们可以修改：&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 打开工程，依次操作菜单如下：Project-&gt;Setting-&gt;Link，在Category 中选中Output，然后在Reserve中设定堆栈的最大值和commit。<br>注意：reserve最小值为4Byte；commit是保留在虚拟内存的页文件里面，它设置的较大会使栈开辟较大的值，可能增加内存的开销和启动时间。<br>&nbsp;&nbsp;&nbsp; 碎片问题：对于堆来讲，频繁的new/delete势必会造成内存空间的不连续，从而造成大量的碎片，使程序效率降低。对于栈来讲，则不会存在这个问题， 因为栈是先进后出的队列，他们是如此的一一对应，以至于永远都不可能有一个内存块从栈中间弹出，在他弹出之前，在他上面的后进的栈内容已经被弹出，详细的 可以参考数据结构，这里我们就不再一一讨论了。<br>&nbsp;&nbsp;&nbsp; 生长方向：对于堆来讲，生长方向是向上的，也就是向着内存地址增加的方向；对于栈来讲，它的生长方向是向下的，是向着内存地址减小的方向增长。<br>&nbsp;&nbsp;&nbsp; 分配方式：堆都是动态分配的，没有静态分配的堆。栈有2种分配方式：静态分配和动态分配。静态分配是编译器完成的，比如局部变量的分配。动态分配由alloca函数进行分配，但是栈的动态分配和堆是不同的，他的动态分配是由编译器进行释放，无需我们手工实现。<br>&nbsp;&nbsp;&nbsp; 分配效率：栈是机器系统提供的数据结构，计算机会在底层对栈提供支持：分配专门的寄存器存放栈的地址，压栈出栈都有专门的指令执行，这就决定了栈的效率比 较高。堆则是C/C++函数库提供的，它的机制是很复杂的，例如为了分配一块内存，库函数会按照一定的算法（具体的算法可以参考数据结构/操作系统）在堆 内存中搜索可用的足够大小的空间，如果没有足够大小的空间（可能是由于内存碎片太多），就有可能调用系统功能去增加程序数据段的内存空间，这样就有机会分 到足够大小的内存，然后进行返回。显然，堆的效率比栈要低得多。<br>&nbsp;&nbsp;&nbsp; 从这里我们可以看到，堆和栈相比，由于大量new/delete的使用，容易造成大量的内存碎片；由于没有专门的系统支持，效率很低；由于可能引发用户态 和核心态的切换，内存的申请，代价变得更加昂贵。所以栈在程序中是应用最广泛的，就算是函数的调用也利用栈去完成，函数调用过程中的参数，返回地址， EBP和局部变量都采用栈的方式存放。所以，我们推荐大家尽量用栈，而不是用堆。<br>&nbsp;&nbsp;&nbsp; 虽然栈有如此众多的好处，但是由于和堆相比不是那么灵活，有时候分配大量的内存空间，还是用堆好一些。<br>&nbsp;&nbsp;&nbsp; 无论是堆还是栈，都要防止越界现象的发生（除非你是故意使其越界），因为越界的结果要么是程序崩溃，要么是摧毁程序的堆、栈结构，产生以想不到的结果,就 算是在你的程序运行过程中，没有发生上面的问题，你还是要小心，说不定什么时候就崩掉，那时候debug可是相当困难的：）<br>对了，还有一件事，如果有人把堆栈合起来说，那它的意思是栈，可不是堆，呵呵，清楚了？<br><strong>static用来控制变量的存储方式和可见性<br></strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 函数内部定义的变量，在程序执行到它的定义处时，编译器为它在栈上分配空间，函数在栈上分配的空间在此函数执行结束时会释放掉，这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时，如何实现？ 最容易想到的方法是定义一个全局的变量，但定义为一个全局变量有许多缺点，最明显的缺点是破坏了此变量的访问范围（使得在此函数中定义的变量，不仅仅受此 函数控制）。
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部，对外不可见。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static的内部机制：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用，所以静态数据成员不能在任何函数内分配空间和初始化。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这样，它的空间分配有三个可能的地方，一是作为类的外部接口的头文件，那里有类声明；二是类定义的内部实现，那里有类的成员函数定义；三是应用程序的main（）函数前的全局数据声明和定义处。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 静态数据成员要实际地分配空间，故不能在类的声明中定义（只能声明数据成员）。类声明只声明一个类的&#8220;尺寸和规格&#8221;，并不进行实际的内存分配，所以在类声 明中写成定义是错误的。它也不能在头文件中类声明的外部定义，因为那会造成在多个使用该类的源文件中，对其重复定义。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static被引入以告知编译器，将变量存储在程序的静态存储区而非栈上空间，静态<br>数据成员按定义出现的先后顺序依次初始化，注意静态成员嵌套时，要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static的优势：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以节省内存，因为它是所有对象所公有的，因此，对多个对象来说，静态数据成员只存储一处，供所有对象共用。静态数据成员的值对每个对象都是一样，但它的 值是可以更新的。只要对静态数据成员的值更新一次，保证所有对象存取更新后的相同的值，这样可以提高时间效率。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 引用静态数据成员时，采用如下格式：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;类名&gt;::&lt;静态成员名&gt;<br>&nbsp;&nbsp;&nbsp; 如果静态数据成员的访问权限允许的话(即public的成员)，可在程序中，按上述格式<br>来引用静态数据成员。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PS:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1)类的静态成员函数是属于整个类而非类的对象，所以它没有this指针，这就导致<br>了它仅能访问类的静态数据和静态成员函数。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2)不能将静态成员函数定义为虚函数。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3)由于静态成员声明于类中，操作于其外，所以对其取地址操作，就多少有些特殊<br>，变量地址是指向其数据类型的指针 ，函数地址类型是一个&#8220;nonmember函数指针&#8221;。 </p>
<p>&#160;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (4)由于静态成员函数没有this指针，所以就差不多等同于nonmember函数，结果就<br>产生了一个意想不到的好处：成为一个callback函数，使得我们得以将C++和C-based X W<br>indow系统结合，同时也成功的应用于线程函数身上。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (5)static并没有增加程序的时空开销，相反她还缩短了子类对父类静态成员的访问<br>时间，节省了子类的内存空间。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (6)静态数据成员在&lt;定义或说明&gt;时前面加关键字static。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (7)静态数据成员是静态存储的，所以必须对它进行初始化。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (8)静态成员初始化与一般数据成员初始化不同:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 初始化在类体外进行，而前面不加static，以免与一般静态变量或对象相混淆；<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 初始化时不加该成员的访问权限控制符private，public等；<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 初始化时使用作用域运算符来标明它所属类；<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 所以我们得出静态数据成员初始化的格式：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;数据类型&gt;&lt;类名&gt;::&lt;静态数据成员名&gt;=&lt;值&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (9)为了防止父类的影响，可以在子类定义一个与父类相同的静态变量，以屏蔽父类的影响。这里有一点需要注意：我们说静态成员为父类和子类共享，但我们有 重复定义了静态成员，这会不会引起错误呢？不会，我们的编译器采用了一种绝妙的手法：name-mangling 用以生成唯一的标志。</p>
<img src ="http://www.cppblog.com/yehao/aggbug/145814.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-06 13:19 <a href="http://www.cppblog.com/yehao/articles/145814.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++堆、栈、自由存储区、全局/静态存储区和常量存储区</title><link>http://www.cppblog.com/yehao/articles/145813.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Fri, 06 May 2011 05:18:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/145813.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/145813.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/145813.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/145813.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/145813.html</trackback:ping><description><![CDATA[<p>一个由c/C++编译的程序占用的内存分为以下几个部分 <br>1、栈区（stack）— 由编译器自动分配释放 ，存放函数的参数值，局部变量的值等。其操作方式类似于数据结构中的栈。 <br>2、堆区（heap） — 一般由程序员分配释放， 若程序员不释放，程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事，分配方式倒是类似于链表，呵呵。 <br>3、全局区（静态区）（static）—，全局变量和静态变量的存储是放在一块的，初始化的全局变量和静态变量在一块区域， 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放 <br>4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放 <br>5、程序代码区—存放函数体的二进制代码。</p>
<p><br>二、例子程序 <br>这是一个前辈写的，非常详细 <br>//main.cpp <br>int a = 0; 全局初始化区 <br>char *p1; 全局未初始化区 <br>main() <br>{ <br>int b; 栈 <br>char s[] = "abc"; 栈 <br>char *p2; 栈 <br>char *p3 = "123456"; 123456在常量区，p3在栈上。 <br>static int c =0； 全局（静态）初始化区 <br>p1 = (char *)malloc(10); <br>p2 = (char *)malloc(20); <br>分配得来得10和20字节的区域就在堆区。 <br>strcpy(p1, "123456"); 123456放在常量区，编译器可能会将它与p3所指向的"123456"优化成一个地方。 <br>}</p>
<p><br>二、堆和栈的理论知识 <br>2.1申请方式 <br>stack: <br>由系统自动分配。 例如，声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间 <br>heap: <br>需要程序员自己申请，并指明大小，在c中malloc函数 <br>如p1 = (char *)malloc(10); <br>在C++中用new运算符 <br>如p2 = (char *)malloc(10); <br>但是注意p1、p2本身是在栈中的。</p>
<p><br>2.2 <br>申请后系统的响应 <br>栈：只要栈的剩余空间大于所申请空间，系统将为程序提供内存，否则将报异常提示栈溢出。 <br>堆：首先应该知道操作系统有一个记录空闲内存地址的链表，当系统收到程序的申请时， <br>会遍历该链表，寻找第一个空间大于所申请空间的堆结点，然后将该结点从空闲结点链表中删除，并将该结点的空间分配给程序，另外，对于大多数系统，会在这块内存空间中的首地址处记录本次分配的大小，这样，代码中的delete语句才能正确的释放本内存空间。另外，由于找到的堆结点的大小不一定正好等于申请的大小，系统会自动的将多余的那部分重新放入空闲链表中。</p>
<p>2.3申请大小的限制 <br>栈：在Windows下,栈是向低地址扩展的数据结构，是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的，在 WINDOWS下，栈的大小是2M（也有的说是1M，总之是一个编译时就确定的常数），如果申请的空间超过栈的剩余空间时，将提示overflow。因此，能从栈获得的空间较小。 <br>堆：堆是向高地址扩展的数据结构，是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的，自然是不连续的，而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见，堆获得的空间比较灵活，也比较大。</p>
<p><br>2.4申请效率的比较： <br>栈由系统自动分配，速度较快。但程序员是无法控制的。 <br>堆是由new分配的内存，一般速度比较慢，而且容易产生内存碎片,不过用起来最方便. <br>另外，在WINDOWS下，最好的方式是用VirtualAlloc分配内存，他不是在堆，也不是在栈是直接在进程的地址空间中保留一快内存，虽然用起来最不方便。但是速度快，也最灵活。</p>
<p>2.5堆和栈中的存储内容 <br>栈： 在函数调用时，第一个进栈的是主函数中后的下一条指令（函数调用语句的下一条可执行语句）的地址，然后是函数的各个参数，在大多数的C编译器中，参数是由右往左入栈的，然后是函数中的局部变量。注意静态变量是不入栈的。 <br>当本次函数调用结束后，局部变量先出栈，然后是参数，最后栈顶指针指向最开始存的地址，也就是主函数中的下一条指令，程序由该点继续运行。 <br>堆：一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。</p>
<p>2.6存取效率的比较</p>
<p>char s1[] = "aaaaaaaaaaaaaaa"; <br>char *s2 = "bbbbbbbbbbbbbbbbb"; <br>aaaaaaaaaaa是在运行时刻赋值的； <br>而bbbbbbbbbbb是在编译时就确定的； <br>但是，在以后的存取中，在栈上的数组比指针所指向的字符串(例如堆)快。 <br>比如： <br>#include <br>void main() <br>{ <br>char a = 1; <br>char c[] = "1234567890"; <br>char *p ="1234567890"; <br>a = c[1]; <br>a = p[1]; <br>return; <br>} <br>对应的汇编代码 <br>10: a = c[1]; <br>00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] <br>0040106A 88 4D FC mov byte ptr [ebp-4],cl <br>11: a = p[1]; <br>0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] <br>00401070 8A 42 01 mov al,byte ptr [edx+1] <br>00401073 88 45 FC mov byte ptr [ebp-4],al <br>第一种在读取时直接就把字符串中的元素读到寄存器cl中，而第二种则要先把指针值读到edx中，在根据edx读取字符，显然慢了。</p>
<p><br>2.7小结： <br>堆和栈的区别可以用如下的比喻来看出： <br>使用栈就象我们去饭馆里吃饭，只管点菜（发出申请）、付钱、和吃（使用），吃饱了就走，不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作，他的好处是快捷，但是自由度小。 <br>使用堆就象是自己动手做喜欢吃的菜肴，比较麻烦，但是比较符合自己的口味，而且自由度大。</p>
<p>1、内存分配方面：</p>
<p>&nbsp;&nbsp;&nbsp; 堆：一般由程序员分配释放， 若程序员不释放，程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事，分配方式是类似于链表。可能用到的关键字如下：new、malloc、delete、free等等。</p>
<p>&nbsp;&nbsp;&nbsp; 栈：由编译器(Compiler)自动分配释放，存放函数的参数值，局部变量的值等。其操作方式类似于数据结构中的栈。</p>
<p>2、申请方式方面：</p>
<p>&nbsp;&nbsp;&nbsp; 堆：需要程序员自己申请，并指明大小。在c中malloc函数如p1 = (char *)malloc(10)；在C++中用new运算符，但是注意p1、p2本身是在栈中的。因为他们还是可以认为是局部变量。</p>
<p>&nbsp;&nbsp;&nbsp; 栈：由系统自动分配。 例如，声明在函数中一个局部变量 int b；系统自动在栈中为b开辟空间。</p>
<p>3、系统响应方面：</p>
<p>&nbsp;&nbsp;&nbsp; 堆：操作系统有一个记录空闲内存地址的链表，当系统收到程序的申请时，会遍历该链表，寻找第一个空间大于所申请空间的堆结点，然后将该结点从空闲结点链表中删除，并将该结点的空间分配给程序，另外，对于大多数系统，会在这块内存空间中的首地址处记录本次分配的大小，这样代码中的delete语句才能正确的释放本内存空间。另外由于找到的堆结点的大小不一定正好等于申请的大小，系统会自动的将多余的那部分重新放入空闲链表中。</p>
<p>&nbsp;&nbsp;&nbsp; 栈：只要栈的剩余空间大于所申请空间，系统将为程序提供内存，否则将报异常提示栈溢出。</p>
<p>4、大小限制方面：</p>
<p>&nbsp;&nbsp;&nbsp; 堆：是向高地址扩展的数据结构，是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的，自然是不连续的，而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见，堆获得的空间比较灵活，也比较大。</p>
<p>&nbsp;&nbsp;&nbsp; 栈：在Windows下, 栈是向低地址扩展的数据结构，是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的，在WINDOWS下，栈的大小是固定的（是一个编译时就确定的常数），如果申请的空间超过栈的剩余空间时，将提示overflow。因此，能从栈获得的空间较小。</p>
<p>5、效率方面：</p>
<p>&nbsp;&nbsp;&nbsp; 堆：是由new分配的内存，一般速度比较慢，而且容易产生内存碎片，不过用起来最方便，另外，在WINDOWS下，最好的方式是用VirtualAlloc分配内存，他不是在堆，也不是在栈是直接在进程的地址空间中保留一快内存，虽然用起来最不方便。但是速度快，也最灵活。</p>
<p>&nbsp;&nbsp;&nbsp; 栈：由系统自动分配，速度较快。但程序员是无法控制的。</p>
<p>6、存放内容方面：</p>
<p>&nbsp;&nbsp;&nbsp; 堆：一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。</p>
<p>&nbsp;&nbsp;&nbsp; 栈：在函数调用时第一个进栈的是主函数中后的下一条指令（函数调用语句的下一条可执行语句）的地址然后是函数的各个参数，在大多数的C编译器中，参数是由右往左入栈，然后是函数中的局部变量。 注意: 静态变量是不入栈的。当本次函数调用结束后，局部变量先出栈，然后是参数，最后栈顶指针指向最开始存的地址，也就是主函数中的下一条指令，程序由该点继续运行。</p>
<p>7、存取效率方面：</p>
<p>&nbsp;&nbsp;&nbsp; 堆：char *s1 = "Hellow Word"；是在编译时就确定的；</p>
<p>&nbsp;&nbsp;&nbsp; 栈：char s1[] = "Hellow Word"； 是在运行时赋值的；用数组比用指针速度要快一些，因为指针在底层汇编中需要用edx寄存器中转一下，而数组在栈上直接读取。</p>
<img src ="http://www.cppblog.com/yehao/aggbug/145813.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-06 13:18 <a href="http://www.cppblog.com/yehao/articles/145813.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>为什么说虚函数的效率低</title><link>http://www.cppblog.com/yehao/articles/145812.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Fri, 06 May 2011 05:02:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/145812.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/145812.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/145812.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/145812.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/145812.html</trackback:ping><description><![CDATA[<p>虚函数联系到多态，多态联系到继承。所以本文中都是在继承层次上做文章。没了继承，什么都没得谈。</p>
<p>　　下面是对C++的虚函数这玩意儿的理解。</p>
<p>　　一， 什么是虚函数（如果不知道虚函数为何物，但有急切的想知道，那你就应该从这里开始）</p>
<p>　　简单地说，那些被virtual关键字修饰的成员函数，就是虚函数。虚函数的作用，用专业术语来解释就是实现多态性（Polymorphism），多态性是将接口与实现进行分离；用形象的语言来解释就是实现以共同的方法，但因个体差异而采用不同的策略。下面来看一段简单的代码</p>
<p>　　class A{</p>
<p>　　public:</p>
<p>　　void print(){ cout&lt;&lt;&#8221;This is A&#8221;&lt;&lt;endl;}</p>
<p>　　};</p>
<p>　　class B:public A{</p>
<p>　　public:</p>
<p>　　void print(){ cout&lt;&lt;&#8221;This is B&#8221;&lt;&lt;endl;}</p>
<p>　　};</p>
<p>　　int main(){ //为了在以后便于区分，我这段main()代码叫做main1</p>
<p>　　A a;</p>
<p>　　B b;</p>
<p>　　a.print();</p>
<p>　　b.print();</p>
<p>　　}</p>
<p>　　通过class A和class B的print()这个接口，可以看出这两个class因个体的差异而采用了不同的策略，输出的结果也是我们预料中的，分别是This is A和This is B。但这是否真正做到了多态性呢？No，多态还有个关键之处就是一切用指向基类的指针或引用来操作对象。那现在就把main()处的代码改一改。</p>
<p>　　int main(){ //main2</p>
<p>　　A a;</p>
<p>　　B b;</p>
<p>　　A* p1=&amp;a;</p>
<p>　　A* p2=&amp;b;</p>
<p>　　p1-&gt;print();</p>
<p>　　p2-&gt;print();</p>
<p>　　}</p>
<p>　　运行一下看看结果，哟呵，蓦然回首，结果却是两个This is A。问题来了，p2明明指向的是class B的对象但却是调用的class A的print()函数，这不是我们所期望的结果，那么解决这个问题就需要用到虚函数</p>
<p>　　class A{</p>
<p>　　public:</p>
<p>　　virtual void print(){ cout&lt;&lt;&#8221;This is A&#8221;&lt;&lt;endl;} //现在成了虚函数了</p>
<p>　　};</p>
<p>　　class B:public A{</p>
<p>　　public:</p>
<p>　　void print(){ cout&lt;&lt;&#8221;This is B&#8221;&lt;&lt;endl;} //这里需要在前面加上关键字virtual吗？</p>
<p>　　};</p>
<p>　　毫无疑问，class A的成员函数print()已经成了虚函数，那么class B的print()成了虚函数了吗？回答是Yes，我们只需在把基类的成员函数设为virtual，其派生类的相应的函数也会自动变为虚函数。所以，class B的print()也成了虚函数。那么对于在派生类的相应函数前是否需要用virtual关键字修饰，那就是你自己的问题了。</p>
<p>　　现在重新运行main2的代码，这样输出的结果就是This is A和This is B了。</p>
<p>　　现在来消化一下，我作个简单的总结，指向基类的指针在操作它的多态类对象时，会根据不同的类对象，调用其相应的函数，这个函数就是虚函数。</p>
<p>　　二， 虚函数是如何做到的（如果你没有看过《Inside The C++ Object Model》这本书，但又急切想知道，那你就应该从这里开始）</p>
<p>　　虚函数是如何做到因对象的不同而调用其相应的函数的呢？现在我们就来剖析虚函数。我们先定义两个类</p>
<p>　　class A{ //虚函数示例代码</p>
<p>　　public:</p>
<p>　　virtual void fun(){cout&lt;&lt;1&lt;&lt;endl;}</p>
<p>　　virtual void fun2(){cout&lt;&lt;2&lt;&lt;endl;}</p>
<p>　　};</p>
<p>　　class B:public A{</p>
<p>　　public:</p>
<p>　　void fun(){cout&lt;&lt;3&lt;&lt;endl;}</p>
<p>　　void fun2(){cout&lt;&lt;4&lt;&lt;endl;}</p>
<p>　　};</p>
<p>　　由于这两个类中有虚函数存在，所以编译器就会为他们两个分别插入一段你不知道的数据，并为他们分别创建一个表。那段数据叫做vptr指针，指向那个表。那个表叫做vtbl，每个类都有自己的vtbl，vtbl的作用就是保存自己类中虚函数的地址，我们可以把vtbl形象地看成一个数组，这个数组的每个元素存放的就是虚函数的地址，请看图</p>
<p>　　通过上图，可以看到这两个vtbl分别为class A和class B服务。现在有了这个模型之后，我们来分析下面的代码</p>
<p>　　A *p=new A;</p>
<p>　　p-&gt;fun();</p>
<p>　　毫无疑问，调用了A::fun()，但是A::fun()是如何被调用的呢？它像普通函数那样直接跳转到函数的代码处吗？No，其实是这样的，首先是取出vptr的值，这个值就是vtbl的地址，再根据这个值来到vtbl这里，由于调用的函数A::fun()是第一个虚函数，所以取出vtbl第一个slot里的值，这个值就是A::fun()的地址了，最后调用这个函数。现在我们可以看出来了，只要vptr不同，指向的vtbl就不同，而不同的vtbl里装着对应类的虚函数地址，所以这样虚函数就可以完成它的任务。</p>
<p>　　而对于class A和class B来说，他们的vptr指针存放在何处呢？其实这个指针就放在他们各自的实例对象里。由于class A和class B都没有数据成员，所以他们的实例对象里就只有一个vptr指针。通过上面的分析，现在我们来实作一段代码，来描述这个带有虚函数的类的简单模型。</p>
<p>　　#include&lt;iostream&gt;</p>
<p>　　using namespace std;</p>
<p>　　//将上面&#8220;虚函数示例代码&#8221;添加在这里</p>
<p>　　int main(){</p>
<p>　　void (*fun)(A*);</p>
<p>　　A *p=new B;</p>
<p>　　long lVptrAddr;</p>
<p>　　memcpy(&amp;lVptrAddr,p,4);</p>
<p>　　memcpy(&amp;fun,reinterpret_cast&lt;long*&gt;(lVptrAddr),4);</p>
<p>　　fun(p);</p>
<p>　　delete p;</p>
<p>　　system("pause");</p>
<p>　　}</p>
<p>　　用VC或Dev-C++编译运行一下，看看结果是不是输出3，如果不是，那么太阳明天肯定是从西边出来。现在一步一步开始分析</p>
<p>　　void (*fun)(A*); 这段定义了一个函数指针名字叫做fun，而且有一个A*类型的参数，这个函数指针待会儿用来保存从vtbl里取出的函数地址</p>
<p>　　A* p=new B; new B是向内存(内存分5个区:全局名字空间,自由存储区,寄存器,代码空间,栈)自由存储区申请一个内存单元的地址然后隐式地保存在一个指针中.然后把这个地址附值给A类型的指针P.</p>
<p>　　.</p>
<p>　　long lVptrAddr; 这个long类型的变量待会儿用来保存vptr的值</p>
<p>　　memcpy(&amp;lVptrAddr,p,4); 前面说了，他们的实例对象里只有vptr指针，所以我们就放心大胆地把p所指的4bytes内存里的东西复制到lVptrAddr中，所以复制出来的4bytes内容就是vptr的值，即vtbl的地址</p>
<p>　　现在有了vtbl的地址了，那么我们现在就取出vtbl第一个slot里的内容</p>
<p>　　memcpy(&amp;fun,reinterpret_cast&lt;long*&gt;(lVptrAddr),4); 取出vtbl第一个slot里的内容，并存放在函数指针fun里。需要注意的是lVptrAddr里面是vtbl的地址，但lVptrAddr不是指针，所以我们要把它先转变成指针类型</p>
<p>　　fun(p); 这里就调用了刚才取出的函数地址里的函数，也就是调用了B::fun()这个函数，也许你发现了为什么会有参数p,其实类成员函数调用时，会有个this指针，这个p就是那个this指针，只是在一般的调用中编译器自动帮你处理了而已，而在这里则需要自己处理。</p>
<p>　　delete p; 释放由p指向的自由空间;</p>
<p>　　system("pause"); 屏幕暂停;</p>
<p>　　如果调用B::fun2()怎么办？那就取出vtbl的第二个slot里的值就行了</p>
<p>　　memcpy(&amp;fun,reinterpret_cast&lt;long*&gt;(lVptrAddr+4),4); 为什么是加4呢？因为一个指针的长度是4bytes，所以加4。或者memcpy(&amp;fun,reinterpret_cast&lt;long*&gt;(lVptrAddr)+1,4); 这更符合数组的用法，因为lVptrAddr被转成了long*型别，所以+1就是往后移sizeof(long)的长度</p>
<p>　　三， 以一段代码开始</p>
<p>　　#include&lt;iostream&gt;</p>
<p>　　using namespace std;</p>
<p>　　class A{ //虚函数示例代码2</p>
<p>　　public:</p>
<p>　　virtual void fun(){ cout&lt;&lt;"A::fun"&lt;&lt;endl;}</p>
<p>　　virtual void fun2(){cout&lt;&lt;"A::fun2"&lt;&lt;endl;}</p>
<p>　　};</p>
<p>　　class B:public A{</p>
<p>　　public:</p>
<p>　　void fun(){ cout&lt;&lt;"B::fun"&lt;&lt;endl;}</p>
<p>　　void fun2(){ cout&lt;&lt;"B::fun2"&lt;&lt;endl;}</p>
<p>　　}; //end//虚函数示例代码2</p>
<p>　　int main(){</p>
<p>　　void (A::*fun)(); //定义一个函数指针</p>
<p>　　A *p=new B;</p>
<p>　　fun=&amp;A::fun;</p>
<p>　　(p-&gt;*fun)();</p>
<p>　　fun = &amp;A::fun2;</p>
<p>　　(p-&gt;*fun)();</p>
<p>　　delete p;</p>
<p>　　system("pause");</p>
<p>　　}</p>
<p>　　你能估算出输出结果吗？如果你估算出的结果是A::fun和A::fun2，呵呵，恭喜恭喜，你中圈套了。其实真正的结果是B::fun和B::fun2，如果你想不通就接着往下看。给个提示，&amp;A::fun和&amp;A::fun2是真正获得了虚函数的地址吗？</p>
<p>　　首先我们回到第二部分，通过段实作代码，得到一个&#8220;通用&#8221;的获得虚函数地址的方法</p>
<p>　　#include&lt;iostream&gt;</p>
<p>　　using namespace std;</p>
<p>　　//将上面&#8220;虚函数示例代码2&#8221;添加在这里</p>
<p>　　void CallVirtualFun(void* pThis,int index=0){</p>
<p>　　void (*funptr)(void*);</p>
<p>　　long lVptrAddr;</p>
<p>　　memcpy(&amp;lVptrAddr,pThis,4);</p>
<p>　　memcpy(&amp;funptr,reinterpret_cast&lt;long*&gt;(lVptrAddr)+index,4);</p>
<p>　　funptr(pThis); //调用</p>
<p>　　}</p>
<p>　　int main(){</p>
<p>　　A* p=new B;</p>
<p>　　CallVirtualFun(p); //调用虚函数p-&gt;fun()</p>
<p>　　CallVirtualFun(p,1);//调用虚函数p-&gt;fun2()</p>
<p>　　system("pause");</p>
<p>　　}</p>
<p>　　现在我们拥有一个&#8220;通用&#8221;的CallVirtualFun方法。</p>
<p>　　这个通用方法和第三部分开始处的代码有何联系呢？联系很大。由于A::fun()和A::fun2()是虚函数，所以&amp;A::fun和&amp;A::fun2获得的不是函数的地址，而是一段间接获得虚函数地址的一段代码的地址，我们形象地把这段代码看作那段CallVirtualFun。编译器在编译时，会提供类似于CallVirtualFun这样的代码，当你调用虚函数时，其实就是先调用的那段类似CallVirtualFun的代码，通过这段代码，获得虚函数地址后，最后调用虚函数，这样就真正保证了多态性。同时大家都说虚函数的效率低，其原因就是，在调用虚函数之前，还调用了获得虚函数地址的代码。</p>
<p>　　最后的说明：本文的代码可以用VC6和Dev-C++4.9.8.0通过编译，且运行无问题。其他的编译器小弟不敢保证。其中，里面的类比方法只能看成模型，因为不同的编译器的低层实现是不同的。例如this指针，Dev-C++的gcc就是通过压栈，当作参数传递，而VC的编译器则通过取出地址保存在ecx中。所以这些类比方法不能当作具体实现</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/hujiangnet/archive/2008/11/01/3201746.aspx">http://blog.csdn.net/hujiangnet/archive/2008/11/01/3201746.aspx</a></p>
<img src ="http://www.cppblog.com/yehao/aggbug/145812.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-06 13:02 <a href="http://www.cppblog.com/yehao/articles/145812.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>#pragma用法详解</title><link>http://www.cppblog.com/yehao/articles/145473.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Sun, 01 May 2011 11:12:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/145473.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/145473.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/145473.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/145473.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/145473.html</trackback:ping><description><![CDATA[<p>目录：<br>（0）&nbsp;&nbsp; 前言<br>（1） #pragma message能够在编译信息输出窗口中输出相应的信息<br>（2） #pragma code_seg能够设置程序中函数代码存放的代码段,开发驱动程序的时会用到<br>（3） #pragma&nbsp; once若用在头文件的最开始处就能够保证头文件被编译一次<br>（4） #pragma&nbsp; hdrstop表示预编译头文件到此为止<br>（5） #pragma&nbsp; resource "*.dfm"表示把*.dfm文件中的资源加入工程<br>（6） #pragma warning允许有选择性的修改编译器的警告消息的行为<br>（7） #pragma comment将一个注释记录放入一个对象文件或可执行文件中<br>（8） #pragma data_seg建立一个新的数据段并定义共享数据<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 应用1：在DLL中定义一个共享的，有名字的数据段<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 应用2： data_seg控制应用程序的启动次数<br>（9） 其他用法</p>
<p><br>&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp;</p>
<p>(0)前言</p>
<p>&nbsp; #Pragma 指令的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma 指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。&nbsp;&nbsp; <br>&nbsp;&nbsp; 其格式一般为:&nbsp;&nbsp; #Pragma&nbsp;&nbsp; Para&nbsp;&nbsp; <br>&nbsp; 其中Para为参数，下面来看一些常用的参数。&nbsp;&nbsp; </p>
<p><br>(1) #Pragma message参数能够在编译信息输出窗口中输出相应的信息</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这对于源代码信息的控制是非常重要的。其使用方法为： Pragma&nbsp;&nbsp; message(&#8220;消息文本&#8221;)&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当我们在程序中定义了许多宏来控制源代码版本的时候，我们自己有可能都会忘记有没有正确的设置这些宏，此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #ifdef&nbsp;&nbsp; _X86&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma message(&#8220;_X86 macro activated!&#8221;)&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #endif&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 若定义了_X86，程序编译时就会在显示&#8220;_X86 macro activated!&#8221;。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了 。&nbsp;&nbsp; </p>
<p>(2) #pragma code_seg能够设置程序中函数代码存放的代码段,</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 开发驱动程序的时候就会使用到它。格式如下：&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma code_seg( [ [ { push | pop}, ] [ identifier, ] ][ "segment-name" [, "segment-class" ] ])<br>&nbsp;&nbsp;&nbsp;&nbsp; 该指令用来指定函数在.obj文件中存放的节,观察OBJ文件可以使用VC自带的dumpbin命令行程序 ,如果code_seg没有带参数的话,则函数在OBJ文件中存放在默认在.text节中。<br>&nbsp;&nbsp;&nbsp; push (可选参数) 将一个记录放到内部编译器的堆栈中,可选参数可以为一个标识符或者节名<br>&nbsp;&nbsp;&nbsp; pop(可选参数) 将一个记录从堆栈顶端弹出,该记录可以为一个标识符或者节名<br>&nbsp;&nbsp;&nbsp; identifier (可选参数) 当使用push指令时,为压入堆栈的记录指派的一个标识符,当该标识符被删除的时候和其相关的堆栈中的记录将被弹出堆栈<br>&nbsp;&nbsp;&nbsp; "segment-name" (可选参数) 表示函数存放的节名<br>例如:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //默认情况下,函数被存放在.text节中<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void func1() {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // stored in .text<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //将函数存放在.my_data1节中<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma code_seg(".my_data1") <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void func2() {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // stored in my_data1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //r1为标识符,将函数放入.my_data2节中<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma code_seg(push, r1, ".my_data2") <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void func3() {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // stored in my_data2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int main() {}</p>
<p><br>&nbsp;(3)#pragma&nbsp; once (比较常用)若用在头文件的最开始处就能够保证头文件被编译一次.</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一般在整个工程中我们只要包含头文件一次就够了，若多个.c/.cpp 文件中都要包含同一个头文件，比如 Windows.h，那很多声明等等岂不是有两次了？解决这个问题的传统的方法是在头文件开始出用 #define 定义一个宏，比如 Windows.h 中:&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #ifndef&nbsp;&nbsp; _WINDOWS_&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #define&nbsp;&nbsp; _WINDOWS_&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #endif<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这样就可以避免被包含多次。但是这样的后果是代码的可读性较差 (个人观点)，VC给我们提供了另外一个途径，那就是在文件的前面加上:&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; once&#8221;&nbsp;&nbsp; </p>
<p><br>&nbsp;&nbsp;&nbsp; <br>(4)#pragma&nbsp; hdrstop表示预编译头文件到此为止</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度，但如果所有头文件都进行预编译又可能占太多磁盘空间，所以使用这个选项排除一些头文件.有时单元之间有依赖关系，比如单元A依赖单元B，所以单元B要先于单元A编译。你可以用#pragma&nbsp;&nbsp; startup指定编译优先级，如果使用了#pragma&nbsp;&nbsp; package(smart_init) ，BCB就会根据优先级的大小先后编译。&nbsp;&nbsp; </p>
<p><br>&nbsp; <br>（5）#pragma&nbsp; resource "*.dfm"表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体外观的定义。&nbsp;&nbsp; </p>
<p><br>（6） #pragma warning允许有选择性的修改编译器的警告消息的行为</p>
<p>指令格式如下:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma warning( warning-specifier : warning-number-list [;warning-specifier :&nbsp; warning-&nbsp; number-list...])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma warning( push[ ,n ] )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma warning( pop )<br>主要用到的警告表示有如下几个:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; once:只显示一次(警告/错误等)消息<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; default:重置编译器的警告行为到默认状态<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,2,3,4:四个警告级别<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; disable:禁止指定的警告信息<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; error:将指定的警告信息作为错误报告<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; warning( disable: 4507&nbsp; 34; once : 4385; error : 164&nbsp;&nbsp; )&nbsp;&nbsp; <br>&nbsp; 等价于：&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; warning(disable:4507&nbsp;&nbsp; 34)&nbsp;&nbsp; //&nbsp; 不显示4507和34号警告信息&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; warning(once:4385)&nbsp;&nbsp; //&nbsp;&nbsp; 4385号警告信息仅报告一次&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; warning(error:164)&nbsp;&nbsp; //&nbsp;&nbsp; 把164号警告信息作为一个错误。&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; warning( push )保存所有警告信息的现有的警告状态。&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; warning( push,n)保存所有警告信息的现有的警告状态，并且把全局警告等级设定为n。&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; warning( pop )向栈中弹出最后一个警告信息，在入栈和出栈之间所作的一切改动取消。例如：&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; warning(&nbsp;&nbsp; push&nbsp;&nbsp; )&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; warning(&nbsp;&nbsp; disable&nbsp;&nbsp; :&nbsp;&nbsp; 4705&nbsp;&nbsp; )&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; warning(&nbsp;&nbsp; disable&nbsp;&nbsp; :&nbsp;&nbsp; 4706&nbsp;&nbsp; )&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; warning(&nbsp;&nbsp; disable&nbsp;&nbsp; :&nbsp;&nbsp; 4707&nbsp;&nbsp; )&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //.......&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; warning(&nbsp;&nbsp; pop&nbsp;&nbsp; )&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在这段代码的最后，重新保存所有的警告信息(包括4705，4706和4707)。&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; </p>
<p><br>（7）pragma&nbsp;&nbsp; comment将一个注释记录放入一个对象文件或可执行文件中</p>
<p>该指令的格式为</p>
<p>&nbsp;#pragma comment( "comment-type" [, commentstring] )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; comment-type(注释类型):可以指定为五种预定义的标识符的其中一种，五种预定义的标识符为:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; compiler:将编译器的版本号和名称放入目标文件中,本条注释记录将被编译器忽略，如果你为该记录类型提供了commentstring参数,编译器将会产生一个警告<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 例如:#pragma comment( compiler )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exestr: 链接时，将commentstring参数放入到可执行文件中,当操作系统加载可执行文件的时候,该参数字符串不会被加载到内存中.但是,该字符串可被dumpbin之类的程序查找出并打印出来,你可以用这个标识符将版本号码之类的信息嵌入到可执行文件中!<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lib:用来将一个库文件链接到目标文件中 <br>比如我们连接的时候用到了WSock32.lib，你当然可以不辞辛苦地把它加入到你的工程中。但是我觉得更方便的方法是使用#pragma指示符，指定要连接的库:&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma&nbsp;&nbsp; comment(lib,&nbsp;&nbsp; "WSock32.lib")<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; linker:将一个链接选项放入目标文件中,你可以使用这个指令来代替由命令行传入的或者在开发环境中设置的链接选项,你可以指定/include选项来强制包含某个对象,例如:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma comment(linker, "/include:__mySymbol")<br>你可以在程序中设置下列链接选项<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /DEFAULTLIB<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /EXPORT<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /INCLUDE<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /MERGE<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; / SECTION<br>&nbsp;&nbsp; 这些选项在这里就不一一说明了,详细信息请看msdn!<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; user:将一般的注释信息放入目标文件中commentstring参数包含注释的文本信息,这个注释记录将被链接器忽略，例如:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma comment( user, "Compiled on " __DATE__ " at " __TIME__ )</p>
<p><br>&nbsp;<br>（8）#pragma data_seg建立一个新的数据段并定义共享数据</p>
<p>格式为：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma data_seg （"shareddata"）<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HWND sharedwnd=NULL;//共享数据<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma data_seg()</p>
<p>应用1：在DLL中定义一个共享的，有名字的数据段。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注意：a、这个数据段中的全局变量能够被多个进程共享。否则多个进程之间无法共享DLL中的全局变量。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b、共享数据必须初始化，否则微软编译器会把没有初始化的数据放到.BSS段中，从而导致多个进程之间的共享行为失败。<br>&nbsp;&nbsp;&nbsp;&nbsp; 假如在一个DLL中这么写：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma data_seg("MyData")<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int g_Value; // 全局变量未初始化<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma data_seg()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DLL提供两个接口函数：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int GetValue()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return g_Value;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void SetValue(int n)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_Value = n;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 然后启动两个都调用了这个DLL的进程A和B，假如A调用了SetValue(5); B接着调用int m = GetValue(); 那么m的值不一定是5，而是个未定义的值。因为DLL中的全局数据对于每一个调用他的进程而言，是私有的，不能共享的。假如您对g_Value进行了初始化，那么g_Value就一定会被放进MyData段中。</p>
<p>换句话说，假如A调用了SetValue(5); B接着调用int m = GetValue(); 那么m的值就一定是5！这就实现了跨进程之间的数据通信！#pragma </p>
<p>应用2： data_seg控制应用程序的启动次数<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 有的时候我们可能想让一个应用程序只启动一次，就像单件模式(singleton)一样，实现的方法可能有多种，这里说说用#pragma data_seg来实现的方法，很是简洁便利。应用程序的入口文件前面加上：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma data_seg("flag_data")<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int app_count = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma data_seg()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma comment(linker,"/SECTION:flag_data,RWS")<br>&nbsp;&nbsp;&nbsp; 然后程序启动的地方加上<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(app_count&gt;0)&nbsp;&nbsp;&nbsp; // 如果计数大于0，则退出应用程序。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //MessageBox(NULL, "已经启动一个应用程序", "Warning", MB_OK);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //printf("no%d application", app_count);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return FALSE;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app_count++;</p>
<p><br>（9）其他用法</p>
<p>&nbsp;&nbsp;&nbsp; 编译程序可以用#pragma指令激活或终止该编译程序支持的一些编译功能。例如，对循环优化功能： <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma loop_opt(on)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 激活 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma loop_opt(off)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 终止 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 有时，程序中会有些函数会使编译器发出你熟知而想忽略的警告，如&nbsp;&nbsp; &#8220;Parameter xxx is never used in function xxx&#8221;，可以这样： <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma warn —100&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Turn off the warning message for warning #100 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int insert_record(REC *r) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { /* function body */ } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #pragma warn +100&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Turn the warning message for warning #100 back on <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 函数会产生一条有唯一特征码100的警告信息，如此可暂时终止该警告。 每个编译器对#pragma的实现不同，在一个编译器中有效在别的编译器中几乎无效。可从编译器的文档中查看。</p>
<p>&nbsp;</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/gueter/archive/2008/03/31/2234072.aspx">http://blog.csdn.net/gueter/archive/2008/03/31/2234072.aspx</a></p>
<img src ="http://www.cppblog.com/yehao/aggbug/145473.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-01 19:12 <a href="http://www.cppblog.com/yehao/articles/145473.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++ 对象的内存布局(上)</title><link>http://www.cppblog.com/yehao/articles/144912.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Sun, 24 Apr 2011 11:15:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/144912.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/144912.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/144912.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/144912.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/144912.html</trackback:ping><description><![CDATA[<p>转自<a href="http://blog.csdn.net/haoel/archive/2008/10/15/3081328.aspx">http://blog.csdn.net/haoel/archive/2008/10/15/3081328.aspx</a><br><br>前言<br>&nbsp;</p>
<p>07年12月，我写了一篇《C++虚函数表解析》的文章，引起了大家的兴趣。有很多朋友对我的文章留了言，有鼓励我的，有批评我的，还有很多问问题的。我在这里一并对大家的留言表示感谢。这也是我为什么再写一篇续言的原因。因为，在上一篇文章中，我用了的示例都是非常简单的，主要是为了说明一些机理上的问题，也是为了图一些表达上方便和简单。不想，这篇文章成为了打开C++对象模型内存布局的一个引子，引发了大家对C++对象的更深层次的讨论。当然，我之前的文章还有很多方面没有涉及，从我个人感觉下来，在谈论虚函数表里，至少有以下这些内容没有涉及：</p>
<p>&nbsp;</p>
<p>1）有成员变量的情况。</p>
<p>2）有重复继承的情况。</p>
<p>3）有虚拟继承的情况。</p>
<p>4）有钻石型虚拟继承的情况。</p>
<p>&nbsp;</p>
<p>这些都是我本篇文章需要向大家说明的东西。所以，这篇文章将会是《C++虚函数表解析》的一个续篇，也是一篇高级进阶的文章。我希望大家在读这篇文章之前对C++有一定的基础和了解，并能先读我的上一篇文章。因为这篇文章的深度可能会比较深，而且会比较杂乱，我希望你在读本篇文章时不会有大脑思维紊乱导致大脑死机的情况。;-)</p>
<p>&nbsp;</p>
<p>对象的影响因素<br>&nbsp;</p>
<p>简而言之，我们一个类可能会有如下的影响因素：</p>
<p>&nbsp;</p>
<p>1）成员变量</p>
<p>2）虚函数（产生虚函数表）</p>
<p>3）单一继承（只继承于一个类）</p>
<p>4）多重继承（继承多个类）</p>
<p>5）重复继承（继承的多个父类中其父类有相同的超类）</p>
<p>6）虚拟继承（使用virtual方式继承，为了保证继承后父类的内存布局只会存在一份）</p>
<p>上述的东西通常是C++这门语言在语义方面对对象内部的影响因素，当然，还会有编译器的影响（比如优化），还有字节对齐的影响。在这里我们都不讨论，我们只讨论C++语言上的影响。</p>
<p>&nbsp;</p>
<p>本篇文章着重讨论下述几个情况下的C++对象的内存布局情况。</p>
<p>&nbsp;</p>
<p>1）单一的一般继承（带成员变量、虚函数、虚函数覆盖）</p>
<p>2）单一的虚拟继承（带成员变量、虚函数、虚函数覆盖）</p>
<p>3）多重继承（带成员变量、虚函数、虚函数覆盖）</p>
<p>4）重复多重继承（带成员变量、虚函数、虚函数覆盖）</p>
<p>5）钻石型的虚拟多重继承（带成员变量、虚函数、虚函数覆盖）</p>
<p>&nbsp;</p>
<p>我们的目标就是，让事情越来越复杂。</p>
<p>&nbsp;</p>
<p>知识复习<br>&nbsp;</p>
<p>我们简单地复习一下，我们可以通过对象的地址来取得虚函数表的地址，如：</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; typedef void(*Fun)(void);<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base b;<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Fun pFun = NULL;<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "虚函数表地址：" &lt;&lt; (int*)(&amp;b) &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "虚函数表 — 第一个函数地址：" &lt;&lt; (int*)*(int*)(&amp;b) &lt;&lt; endl;<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Invoke the first virtual function&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)*((int*)*(int*)(&amp;b));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();<br>&nbsp;</p>
<p>我们同样可以用这种方式来取得整个对象实例的内存布局。因为这些东西在内存中都是连续分布的，我们只需要使用适当的地址偏移量，我们就可以获得整个内存对象的布局。</p>
<p>&nbsp;</p>
<p>本篇文章中的例程或内存布局主要使用如下编译器和系统：</p>
<p>1）Windows XP 和 VC++ 2003</p>
<p>2）Cygwin 和 G++ 3.4.4</p>
<p>&nbsp;</p>
<p>单一的一般继承<br>&nbsp;</p>
<p>下面，我们假设有如下所示的一个继承关系：</p>
<p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/EntryImages/20081015/dd01.jpg" width=177 height=366><br>请注意，在这个继承关系中，父类，子类，孙子类都有自己的一个成员变量。而了类覆盖了父类的f()方法，孙子类覆盖了子类的g_child()及其超类的f()。</p>
<p>&nbsp;</p>
<p>我们的源程序如下所示：</p>
<p>&nbsp;</p>
<p>class Parent {<br>public:<br>&nbsp;&nbsp;&nbsp; int iparent;<br>&nbsp;&nbsp;&nbsp; Parent ():iparent (10) {}<br>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; " Parent::f()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; " Parent::g()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; " Parent::h()" &lt;&lt; endl; }<br>&nbsp;</p>
<p>};<br>&nbsp;</p>
<p>class Child : public Parent {<br>public:<br>&nbsp;&nbsp;&nbsp; int ichild;<br>&nbsp;&nbsp;&nbsp; Child():ichild(100) {}<br>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Child::f()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void g_child() { cout &lt;&lt; "Child::g_child()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void h_child() { cout &lt;&lt; "Child::h_child()" &lt;&lt; endl; }<br>};<br>&nbsp;</p>
<p>class GrandChild : public Child{<br>public:<br>&nbsp;&nbsp;&nbsp; int igrandchild;<br>&nbsp;&nbsp;&nbsp; GrandChild():igrandchild(1000) {}<br>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "GrandChild::f()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void g_child() { cout &lt;&lt; "GrandChild::g_child()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void h_grandchild() { cout &lt;&lt; "GrandChild::h_grandchild()" &lt;&lt; endl; }<br>};<br>我们使用以下程序作为测试程序：（下面程序中，我使用了一个int** pVtab 来作为遍历对象内存布局的指针，这样，我就可以方便地像使用数组一样来遍历所有的成员包括其虚函数表了，在后面的程序中，我也是用这样的方法的，请不必感到奇怪，）</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; typedef void(*Fun)(void);</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; GrandChild gc; <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; int** pVtab = (int**)&amp;gc;<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[0] GrandChild::_vptr-&gt;" &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp; for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][i];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp; ["&lt;&lt;i&lt;&lt;"] ";<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[1] Parent.iparent = " &lt;&lt; (int)pVtab[1] &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[2] Child.ichild = " &lt;&lt; (int)pVtab[2] &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[3] GrandChild.igrandchild = " &lt;&lt; (int)pVtab[3] &lt;&lt; endl;<br>&nbsp;</p>
<p>其运行结果如下所示：（在VC++ 2003和G++ 3.4.4下）</p>
<p>&nbsp;<!--startfragment --></p>
<div><img src="file:///C:/Documents%20and%20Settings/Administrator/Application%20Data/Tencent/Users/515805655/QQ/WinTemp/RichOle/GFWJ%7D6H7[UO4%60@_YQC1JY8J.jpg"> </div>
<p>&nbsp;</p>
<p>使用图片表示如下：</p>
<p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/EntryImages/20081015/dd02.jpg" width=500 height=237><br>可见以下几个方面：</p>
<p>1）虚函数表在最前面的位置。</p>
<p>2）成员变量根据其继承和声明顺序依次放在后面。</p>
<p>3）在单一的继承中，被overwrite的虚函数在虚函数表中得到了更新。<br></p>
<h1><span><font color=#000000 size=5>多重继承</font></span></h1>
<p><font color=#000000 size=3>&nbsp;</font></p>
<p><font size=3><font color=#000000><span>下面，再让我们来看看多重继承中的情况，假设有下面这样一个类的继承关系。注意：子类只</span>overwrite<span>了父类的</span>f()<span>函数，而还有一个是自己的函数（我们这样做的目的是为了用</span>g1()<span>作为一个标记来标明子类的虚函数表）。而且每个类中都有一个自己的成员变量：</span></font></font></p>
<p><font color=#000000 size=3>&nbsp;</font></p>
<p align=center><font color=#000000 size=3></font></p>
<p align=center><font color=#000000 size=3><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/EntryImages/20081015/dd03.jpg" width=328 height=265><br>我们的类继承的源代码如下所示：父类的成员初始为10，20，30，子类的为100</font></p>
<p align=center><font color=#000000 size=3>&nbsp;</font></p>
<p align=center><font color=#000000 size=3>class Base1 {<br>public:<br>&nbsp;&nbsp;&nbsp; int ibase1;<br>&nbsp;&nbsp;&nbsp; Base1():ibase1(10) {}<br>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base1::f()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base1::g()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base1::h()" &lt;&lt; endl; }<br>&nbsp;</font></p>
<p align=center><font color=#000000 size=3>};<br>&nbsp;</font></p>
<p align=center><font color=#000000 size=3>class Base2 {<br>public:<br>&nbsp;&nbsp;&nbsp; int ibase2;<br>&nbsp;&nbsp;&nbsp; Base2():ibase2(20) {}<br>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base2::f()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base2::g()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base2::h()" &lt;&lt; endl; }<br>};<br>&nbsp;</font></p>
<p align=center><font color=#000000 size=3>class Base3 {<br>public:<br>&nbsp;&nbsp;&nbsp; int ibase3;<br>&nbsp;&nbsp;&nbsp; Base3():ibase3(30) {}<br>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base3::f()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base3::g()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base3::h()" &lt;&lt; endl; }<br>};<br>&nbsp;</font></p>
<p align=center><font color=#000000 size=3>&nbsp;</font></p>
<p align=center><font color=#000000 size=3>class Derive : public Base1, public Base2, public Base3 {<br>public:<br>&nbsp;&nbsp;&nbsp; int iderive;<br>&nbsp;&nbsp;&nbsp; Derive():iderive(100) {}<br>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Derive::f()" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp; virtual void g1() { cout &lt;&lt; "Derive::g1()" &lt;&lt; endl; }<br>};</font></p>
<p align=center><font color=#000000 size=3>&nbsp;</font></p>
<p align=center><font color=#000000 size=3>我们通过下面的程序来查看子类实例的内存布局：下面程序中，注意我使用了一个s变量，其中用到了sizof(Base)来找下一个类的偏移量。（因为我声明的是int成员，所以是4个字节，所以没有对齐问题。关于内存的对齐问题，大家可以自行试验，我在这里就不多说了）</font></p>
<p align=center><font color=#000000 size=3>&nbsp;</font></p>
<p align=center><font color=#000000 size=3>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; typedef void(*Fun)(void);</font></p>
<font color=#000000 size=3>
<p align=center><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Derive d;<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int** pVtab = (int**)&amp;d;<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[0] Base1::_vptr-&gt;" &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][0];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] ";<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] ";pFun();<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][2];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] ";pFun();<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][3];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [3] "; pFun();<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][4];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [4] "; cout&lt;&lt;pFun&lt;&lt;endl;<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[1] Base1.ibase1 = " &lt;&lt; (int)pVtab[1] &lt;&lt; endl;<br>&nbsp;</p>
<p align=center>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int s = sizeof(Base1)/4;<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[" &lt;&lt; s &lt;&lt; "] Base2::_vptr-&gt;"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][0];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] "; pFun();<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Fun = (Fun)pVtab[s][1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] "; pFun();<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][2];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] "; pFun(); <br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][3];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [3] ";<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;pFun&lt;&lt;endl;<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "["&lt;&lt; s+1 &lt;&lt;"] Base2.ibase2 = " &lt;&lt; (int)pVtab[s+1] &lt;&lt; endl;<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s = s + sizeof(Base2)/4;</p>
<p align=center><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[" &lt;&lt; s &lt;&lt; "] Base3::_vptr-&gt;"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][0];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] "; pFun();<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] "; pFun();<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][2];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] "; pFun(); <br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][3];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [3] ";<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;pFun&lt;&lt;endl;<br>&nbsp;</p>
<p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s++;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "["&lt;&lt; s &lt;&lt;"] Base3.ibase3 = " &lt;&lt; (int)pVtab[s] &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s++;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "["&lt;&lt; s &lt;&lt;"] Derive.iderive = " &lt;&lt; (int)pVtab[s] &lt;&lt; endl;</p>
<p align=center>&nbsp;</p>
<p align=center>其运行结果如下所示：（在VC++ 2003和G++ 3.4.4下）<br><!--startfragment --></p>
<div><img src="file:///C:/Documents%20and%20Settings/Administrator/Application%20Data/Tencent/Users/515805655/QQ/WinTemp/RichOle/749IM$%25]6OLMJ%7DT112~0_%7BU.jpg"> </div>
<span><font color=#000000 size=3>使用图片表示是下面这个样子：</font></span>
<p><font color=#000000 size=3>&nbsp;</font></p>
<p align=center><font color=#000000 size=3></font></p>
<p align=center><font color=#000000 size=3><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/EntryImages/20081015/dd04.jpg" width=500 height=287></font></p>
<p><span><font color=#000000 size=3>我们可以看到：</font></span></p>
<p><font color=#000000><span><span><font size=3>1）</font>&nbsp;</span></span><span><font size=3>每个父类都有自己的虚表。</font></span></font></p>
<p><font color=#000000><span><span><font size=3>2）</font>&nbsp;</span></span><span><font size=3>子类的成员函数被放到了第一个父类的表中。</font></span></font></p>
<p><font color=#000000><span><span><font size=3>3）</font>&nbsp;</span></span><span><font size=3>内存布局中，其父类布局依次按声明顺序排列。</font></span></font></p>
<p><font color=#000000><span><span><font size=3>4）</font>&nbsp;</span></span><font size=3><span>每个父类的虚表中的</span>f()<span>函数都被</span>overwrite<span>成了子类的</span></font><font size=3>f()</font><span><font size=3>。这样做就是为了解决不同的父类类型的指针指向同一个子类实例，而能够调用到实际的函数。</font></span></font></p>
</font>
<img src ="http://www.cppblog.com/yehao/aggbug/144912.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-04-24 19:15 <a href="http://www.cppblog.com/yehao/articles/144912.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++ 虚函数表解析</title><link>http://www.cppblog.com/yehao/articles/144911.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Sun, 24 Apr 2011 11:07:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/144911.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/144911.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/144911.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/144911.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/144911.html</trackback:ping><description><![CDATA[<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/haoel/archive/2007/12/18/1948051.aspx">http://blog.csdn.net/haoel/archive/2007/12/18/1948051.aspx</a><br><br>前言<br>&nbsp;</p>
<p>C++中的虚函数的作用主要是实现了多态的机制。关于多态，简而言之就是用父类型别的指针指向其子类的实例，然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有&#8220;多种形态&#8221;，这是一种泛型技术。所谓泛型技术，说白了就是试图使用不变的代码来实现可变的算法。比如：模板技术，RTTI技术，虚函数技术，要么是试图做到在编译时决议，要么试图做到运行时决议。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>关于虚函数的使用方法，我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中，我只想从虚函数的实现机制上面为大家 一个清晰的剖析。</p>
<p>&nbsp;</p>
<p>当然，相同的文章在网上也出现过一些了，但我总感觉这些文章不是很容易阅读，大段大段的代码，没有图片，没有详细的说明，没有比较，没有举一反三。不利于学习和阅读，所以这是我想写下这篇文章的原因。也希望大家多给我提意见。</p>
<p>&nbsp;</p>
<p>言归正传，让我们一起进入虚函数的世界。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>虚函数表<br>&nbsp;</p>
<p>对C++ 了解的人都应该知道虚函数（Virtual Function）是通过一张虚函数表（Virtual Table）来实现的。简称为V-Table。在这个表中，主是要一个类的虚函数的地址表，这张表解决了继承、覆盖的问题，保证其容真实反应实际的函数。这样，在有虚函数的类的实例中这个表被分配在了这个实例的内存中，所以，当我们用父类的指针来操作一个子类的时候，这张虚函数表就显得由为重要了，它就像一个地图一样，指明了实际所应该调用的函数。</p>
<p>&nbsp;</p>
<p>这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置（这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下）。 这意味着我们通过对象实例的地址得到这张虚函数表，然后就可以遍历其中函数指针，并调用相应的函数。</p>
<p>&nbsp;</p>
<p>听我扯了那么多，我可以感觉出来你现在可能比以前更加晕头转向了。 没关系，下面就是实际的例子，相信聪明的你一看就明白了。</p>
<p>&nbsp;</p>
<p>假设我们有这样的一个类：</p>
<p>&nbsp;</p>
<p>class Base {<br>&nbsp;&nbsp;&nbsp;&nbsp; public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base::f" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base::g" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base::h" &lt;&lt; endl; }<br>&nbsp;</p>
<p>};<br>&nbsp;</p>
<p>按照上面的说法，我们可以通过Base的实例来得到虚函数表。 下面是实际例程：</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; typedef void(*Fun)(void);<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base b;<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Fun pFun = NULL;<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "虚函数表地址：" &lt;&lt; (int*)(&amp;b) &lt;&lt; endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "虚函数表 — 第一个函数地址：" &lt;&lt; (int*)*(int*)(&amp;b) &lt;&lt; endl;<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Invoke the first virtual function&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)*((int*)*(int*)(&amp;b));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();<br>&nbsp;</p>
<p>实际运行经果如下：(Windows XP+VS2003,&nbsp; Linux 2.6.22 + GCC 4.1.3)</p>
<p>&nbsp;</p>
<p>虚函数表地址：0012FED4<br>虚函数表 — 第一个函数地址：0044F148<br>Base::f<br>&nbsp;</p>
<p>&nbsp;</p>
<p>通过这个示例，我们可以看到，我们可以通过强行把&amp;b转成int *，取得虚函数表的地址，然后，再次取址就可以得到第一个虚函数的地址了，也就是Base::f()，这在上面的程序中得到了验证（把int* 强制转成了函数指针）。通过这个示例，我们就可以知道如果要调用Base::g()和Base::h()，其代码如下：</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (Fun)*((int*)*(int*)(&amp;b)+0);&nbsp; // Base::f()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (Fun)*((int*)*(int*)(&amp;b)+1);&nbsp; // Base::g()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (Fun)*((int*)*(int*)(&amp;b)+2);&nbsp; // Base::h()<br>&nbsp;</p>
<p>这个时候你应该懂了吧。什么？还是有点晕。也是，这样的代码看着太乱了。没问题，让我画个图解释一下。如下所示：</p>
<p><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN><font size=3><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable1.jpg"><br>注意：在上面这个图中，我在虚函数表的最后多加了一个结点，这是虚函数表的结束结点，就像字符串的结束符&#8220;\0&#8221;一样，其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下，这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下，这个值是如果1，表示还有下一个虚函数表，如果值是0，表示是最后一个虚函数表。</font></span></p>
<p><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN><font size=3>&nbsp;</font></span></p>
<p><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN><font size=3>&nbsp;</font></span></p>
<p><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN><font size=3>下面，我将分别说明&#8220;无覆盖&#8221;和&#8220;有覆盖&#8221;时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况，主要目的是为了给一个对比。在比较之下，我们可以更加清楚地知道其内部的具体实现。</font></span></p>
<p><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN><font size=3>&nbsp;</font></span></p>
<p><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN><font size=3>一般继承（无虚函数覆盖）<br>&nbsp;</font></span></p>
<p><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN><font size=3>下面，再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系：</font></span></p>
<p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_Drawing3.jpg"><br></p>
<p style="MARGIN: 0in 0in 0pt" class=MsoNormal><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN><font size=3>请注意，在这个继承关系中，子类没有重载任何父类的函数。那么，在派生类的实例中，其虚函数表如下所示：</font></span></p>
<p style="MARGIN: 0in 0in 0pt" class=MsoNormal><font size=3><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN>对于实例：</span><font face="Times New Roman">Derive d; </font><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN>的虚函数表如下：</span></font></p>
<span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN><font size=3><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'" lang=ZH-CN><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable2.JPG"><br>
<p><span><font size=3>我们可以看到下面几点：</font></span></p>
<p><font size=3><span><span><font face="Times New Roman">1）</font></span></span><span>虚函数按照其声明顺序放于表中。</span></font></p>
<p><font size=3><span><span><font face="Times New Roman">2）</font></span></span><span>父类的虚函数在子类的虚函数前面。</span></font></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><span><font size=3>我相信聪明的你一定可以参考前面的那个程序，来编写一段程序来验证。</font></span></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<h1><span><font size=5>一般继承（有虚函数覆盖）</font></span></h1>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><span><font size=3>覆盖父类的虚函数是很显然的事情，不然，虚函数就变得毫无意义。下面，我们来看一下，如果子类中有虚函数重载了父类的虚函数，会是一个什么样子？假设，我们有下面这样的一个继承关系。</font></span></p>
<img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_Drawing4.jpg"><br>
<p><font size=3><span>为了让大家看到被继承过后的效果，在这个类的设计中，我只覆盖了父类的一个函数：</span><font face="Times New Roman">f()</font><span>。那么，对于派生类的实例，其虚函数表会是下面的一个样子：</span></font></p>
<p align=center><font size=3 face="Times New Roman"><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable3.JPG"></font></p>
<p>&nbsp;</p>
<p><font size=3 face="Times New Roman"></font></p>
<p><span><font size=3>我们从表中可以看到下面几点，</font></span></p>
<p><font size=3><span><span><font face="Times New Roman">1）</font></span></span><span>覆盖的</span><font face="Times New Roman">f()</font><span>函数被放到了虚表中原来父类虚函数的位置。</span></font></p>
<p><font size=3><span><span><font face="Times New Roman">2）</font></span></span><span>没有被覆盖的函数依旧。</span></font></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><span><font size=3>这样，我们就可以看到对于下面这样的程序，</font></span></p>
<p><span><font size=3>&nbsp;</font></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>Base *b = <span>new</span> Derive();</span></p>
<p>b-&gt;f(); </p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><font size=3><span>由</span><font face="Times New Roman">b</font><span>所指的内存中的虚函数表的</span><font face="Times New Roman">f()</font><span>的位置已经被</span><font face="Times New Roman">Derive::f()</font><span>函数地址所取代，于是在实际调用发生时，是</span><font face="Times New Roman">Derive::f()</font><span>被调用了。这就实现了多态。</span></font></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<h1><span><font size=5>多重继承（无虚函数覆盖）</font></span></h1>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><span><font size=3>下面，再让我们来看看多重继承中的情况，假设有下面这样一个类的继承关系。注意：子类并没有覆盖父类的函数。</font></span></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p align=center><font size=3 face="Times New Roman"></font></p>
<p align=center><font size=3 face="Times New Roman"><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_Drawing1.jpg">&nbsp;</font></p>
<p align=center><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><span><font size=3>对于子类实例中的虚函数表，是下面这个样子：</font></span></p>
<p align=center><span><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable4.JPG"></span></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p align=center><font size=3 face="Times New Roman"></font></p>
<p><span><font size=3>我们可以看到：</font></span></p>
<p><span><span><font face="Times New Roman"><font size=3>1）</font>&nbsp;</font></span></span><span><font size=3>每个父类都有自己的虚表。</font></span></p>
<p><span><span><font face="Times New Roman"><font size=3>2）</font>&nbsp;</font></span></span><span><font size=3>子类的成员函数被放到了第一个父类的表中。（所谓的第一个父类是按照声明顺序来判断的）</font></span></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><span><font size=3>这样做就是为了解决不同的父类类型的指针指向同一个子类实例，而能够调用到实际的函数。</font></span></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<h1><span><font size=5>多重继承（有虚函数覆盖）</font></span></h1>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><span><font size=3>下面我们再来看看，如果发生虚函数覆盖的情况。</font></span></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><font size=3><span>下图中，我们在子类中覆盖了父类的</span><font face="Times New Roman">f()</font><span>函数。</span></font></p>
<p align=center><font size=3 face="Times New Roman"><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_Drawing2.jpg">&nbsp;</font></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p><span><font size=3>下面是对于子类实例中的虚函数表的图：</font></span></p>
<p><font size=3 face="Times New Roman">&nbsp;</font></p>
<p align=center><font size=3 face="Times New Roman"></font></p>
<p align=center><font size=3 face="Times New Roman"><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable5.jpg"></font></p>
<p align=center>&nbsp;</p>
<p><font size=3><span>我们可以看见，三个父类虚函数表中的</span><font face="Times New Roman">f()</font><span>的位置被替换成了子类的函数指针。这样，我们就可以任一静态类型的父类来指向子类，并调用子类的</span><font face="Times New Roman">f()</font><span>了。如：</span></font></p>
<p>Derive d;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base1 *b1 = &amp;d;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base2 *b2 = &amp;d;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base3 *b3 = &amp;d;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b1-&gt;f(); //Derive::f()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b2-&gt;f(); //Derive::f()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b3-&gt;f(); //Derive::f()<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b1-&gt;g(); //Base1::g()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b2-&gt;g(); //Base2::g()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b3-&gt;g(); //Base3::g()<br>&nbsp;</p>
<p>&nbsp;</p>
<p>安全性<br>&nbsp;</p>
<p>每次写C++的文章，总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述，相信我们对虚函数表有一个比较细致的了解了。水可载舟，亦可覆舟。下面，让我们来看看我们可以用虚函数表来干点什么坏事吧。</p>
<p>&nbsp;</p>
<p>一、通过父类型的指针访问子类自己的虚函数<br>我们知道，子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数，但我们根本不可能使用下面的语句来调用子类的自有虚函数：</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base1 *b1 = new Derive();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b1-&gt;f1();&nbsp; //编译出错<br>&nbsp;</p>
<p>任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法，所以，这样的程序根本无法编译通过。但在运行时，我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。（关于这方面的尝试，通过阅读后面附录的代码，相信你可以做到这一点）</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>二、访问non-public的虚函数<br>另外，如果父类的虚函数是private或是protected的，但这些非public的虚函数同样会存在于虚函数表中，所以，我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数，这是很容易做到的。</p>
<p>&nbsp;</p>
<p>如：</p>
<p>&nbsp;</p>
<p>class Base {<br>&nbsp;&nbsp;&nbsp; private:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base::f" &lt;&lt; endl; }<br>&nbsp;</p>
<p>};<br>&nbsp;</p>
<p>class Derive : public Base{<br>&nbsp;</p>
<p>};<br>&nbsp;</p>
<p>typedef void(*Fun)(void);<br>&nbsp;</p>
<p>void main() {<br>&nbsp;&nbsp;&nbsp; Derive d;<br>&nbsp;&nbsp;&nbsp; Fun&nbsp; pFun = (Fun)*((int*)*(int*)(&amp;d)+0);<br>&nbsp;&nbsp;&nbsp; pFun();<br>}<br>&nbsp;</p>
<p>&nbsp;</p>
<p>结束语<br>C++这门语言是一门Magic的语言，对于程序员来说，我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言，我们就必需要了解C++里面的那些东西，需要去了解C++中那些危险的东西。不然，这是一种搬起石头砸自己脚的编程语言。</p>
<p>&nbsp;</p>
<p>在文章束之前还是介绍一下自己吧。我从事软件研发有十个年头了，目前是软件开发技术主管，技术方面，主攻Unix/C/C++，比较喜欢网络上的技术，比如分布式计算，网格计算，P2P，Ajax等一切和互联网相关的东西。管理方面比较擅长于团队建设，技术趋势分析，项目管理。欢迎大家和我交流，我的MSN和Email是：<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#104;&#97;&#111;&#101;&#108;&#64;&#104;&#111;&#116;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109;">haoel@hotmail.com</a>&nbsp; </p>
<p>&nbsp;</p>
<p>附录一：VC中查看虚函数表<br>&nbsp;</p>
<p>我们可以在VC的IDE环境中的Debug状态下展开类的实例就可以看到虚函数表了（并不是很完整的）<br></p>
<p align=center><font size=3><span><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable_vc.JPG"></span></font></p>
<p align=center><font size=3></font></p>
<h1><font size=5><span>附录</span><span> </span><span>二：例程</span></font></h1>
<p><span><font size=3>下面是一个关于多重继承的虚函数表访问的例程：</font></span></p>
<p>#include &lt;iostream&gt;<br>using namespace std;<br>&nbsp;</p>
<p>class Base1 {<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base1::f" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base1::g" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base1::h" &lt;&lt; endl; }<br>&nbsp;</p>
<p>};<br>&nbsp;</p>
<p>class Base2 {<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base2::f" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base2::g" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base2::h" &lt;&lt; endl; }<br>};<br>&nbsp;</p>
<p>class Base3 {<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base3::f" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base3::g" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base3::h" &lt;&lt; endl; }<br>};<br>&nbsp;</p>
<p>&nbsp;</p>
<p>class Derive : public Base1, public Base2, public Base3 {<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Derive::f" &lt;&lt; endl; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void g1() { cout &lt;&lt; "Derive::g1" &lt;&lt; endl; }<br>};<br>&nbsp;</p>
<p>&nbsp;</p>
<p>typedef void(*Fun)(void);<br>&nbsp;</p>
<p>int main() <br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Fun pFun = NULL;<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Derive d;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int** pVtab = (int**)&amp;d;<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Base1's vtable<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+0)+0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][0];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+0)+1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+0)+2);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][2];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Derive's vtable<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+0)+3);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][3];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //The tail of the vtable<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][4];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;pFun&lt;&lt;endl;<br>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Base2's vtable<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+1)+0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[1][0];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+1)+1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[1][1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[1][2];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun(); <br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //The tail of the vtable<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[1][3];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;pFun&lt;&lt;endl;<br>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Base3's vtable<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+1)+0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[2][0];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+1)+1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[2][1];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[2][2];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun(); <br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //The tail of the vtable<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[2][3];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;pFun&lt;&lt;endl;<br>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br>}</span></span></font></span></p>
<img src ="http://www.cppblog.com/yehao/aggbug/144911.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-04-24 19:07 <a href="http://www.cppblog.com/yehao/articles/144911.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++对象模型系列</title><link>http://www.cppblog.com/yehao/articles/144143.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Wed, 13 Apr 2011 10:29:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/144143.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/144143.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/144143.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/144143.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/144143.html</trackback:ping><description><![CDATA[<div id=cnblogs_post_body>[C++对象模型][1]目录与参考
<p>C++对象模型系列：</p>
<p>本系列是主要是作者经验的总结且同时参考了大量的网络文章，希望能够给C++的学习者有所帮助，但是由于作者水平有限，难免有错，希望大家能够指出，我将虚心地向大家学习，与大家共同进步！本系列的开发环境是Windows 32+VS2008。</p>
<p>文章：</p>
<p>&nbsp;<a href="http://www.cnblogs.com/itech/archive/2009/02/20/1394272.html" target=_blank><u><font color=#0066cc>指针和引用</font></u></a><br>&nbsp;<a href="http://www.cnblogs.com/itech/archive/2009/02/22/1395434.html" target=_blank><u><font color=#0066cc>指针与数组</font></u></a><br>&nbsp;<a href="http://www.cnblogs.com/itech/archive/2009/02/22/1395491.html" target=_blank><u><font color=#0066cc>指针与字符串</font></u></a><br>&nbsp;<a href="http://www.cnblogs.com/itech/archive/2009/02/23/1395515.html" target=_blank><u><font color=#0066cc>堆栈与函数调用</font></u></a><br>&nbsp;<a href="http://www.cnblogs.com/itech/archive/2009/02/25/1396740.html" target=_blank><u><font color=#0066cc>sizeof与对象内存布局</font></u></a><br>&nbsp;<a href="http://www.cnblogs.com/itech/archive/2009/02/27/1398224.html" target=_blank><u><font color=#0066cc>单继承与虚函数表<br></font></u></a>&nbsp;<a href="http://www.cnblogs.com/itech/archive/2009/02/28/1399995.html" target=_blank><u><font color=#0066cc>多重继承与虚函数表<br></font></u></a>&nbsp;<a href="http://www.cnblogs.com/itech/archive/2009/02/27/1399996.html" target=_blank><u><font color=#0066cc>虚继承与虚函数表</font></u></a><br>&nbsp;<a href="http://www.cnblogs.com/itech/archive/2009/02/25/1398230.html" target=_blank><u><font color=#0066cc>类型转化</font></u></a></p>
<p>参考：</p>
<p>1) C++对象模型<br>&nbsp;C++对象模型笔记：http://blog.csdn.net/ZengMuAnSha/archive/2004/10/13/135477.aspx<br>&nbsp;C++对象内存布局1：http://blog.csdn.net/haoel/archive/2008/10/15/3081328.aspx<br>&nbsp;C++对象内存布局1：http://blog.csdn.net/haoel/archive/2008/10/15/3081385.aspx<br>&nbsp;C++虚函数表解析： http://blog.csdn.net/haoel/archive/2007/12/18/1948051.aspx<br>&nbsp;字节对齐1：http://blog.csdn.net/xuegao007/archive/2007/07/26/1708349.aspx<br>&nbsp;字节对齐2：http://blog.csdn.net/xuegao007/archive/2007/07/26/1708355.aspx<br>&nbsp;字节对齐3：http://blog.csdn.net/xuegao007/archive/2007/07/26/1708360.aspx<br>&nbsp;sizeof : http://blog.csdn.net/xuegao007/archive/2007/08/23/1756047.aspx<br>&nbsp;C++内存对象大会战1：http://blog.csdn.net/xuegao007/archive/2007/08/03/1723765.aspx<br>&nbsp;C++内存对象大会战2：http://blog.csdn.net/xuegao007/archive/2007/08/03/1723766.aspx<br>&nbsp;C++内存管理详解 ： http://www.cnblogs.com/dazhong/articles/721704.html<br>&nbsp;C++内存布局 ：http://www.cnblogs.com/len3d/archive/2007/09/26/906899.html <br>&nbsp;Visual C++ 8.0对象布局的奥秘：虚函数、多继承、虚拟继承 ： http://www.cnblogs.com/neoragex2002/archive/2007/11/01/VC8_Object_Layout_Secret.html <br>&nbsp;关于typeid和RTTI的问答&nbsp; ：http://blog.csdn.net/gxj1680/archive/2009/01/05/3715364.aspx<br>&nbsp;字符串与数组工具收藏：http://tfzxbookshell.spaces.live.com/blog/cns!EB39D7FA27BCD1A1!902.entry<br>&nbsp;memset ,memcpy 和strcpy 的根本区别？:http://tfzxbookshell.spaces.live.com/blog/cns!EB39D7FA27BCD1A1!904.entry<br>&nbsp;函数调用约定和堆栈：http://www.fmddlmyy.cn/text12.html<br>&nbsp;Win32程序函数调用时堆栈变化情况分析收藏：<br>http://www.cnblogs.com/hellohuan/archive/2008/07/07/1237371.html<br>http://blog.csdn.net/wenjie2005/archive/2008/01/09/2031377.aspx<br>C++ sizeof 使用规则及陷阱分析: http://freeman.cnblogs.com/articles/sizeof.html<br>What static_cast&lt;&gt; is actually doing:http://www.codeproject.com/KB/cpp/static_cast.aspx<br>Using generics in C++/CLI:http://www.codeproject.com/KB/mcpp/cppcligenerics.aspx<br>Type Casting:<br>http://www.cplusplus.com/doc/tutorial/typecasting.html<br>http://en.wikipedia.org/wiki/Typeid<br>type_info:<br>http://msdn.microsoft.com/zh-cn/library/70ky2y6k(en-us,VS.80).aspx<br>http://www.cplusplus.com/reference/std/typeinfo/type_info.html<br>http://www.informit.com/guides/content.aspx?g=cplusplus&amp;seqNum=120<br>http://www.atnf.csiro.au/computing/software/sol2docs/manuals/c++/prog_guide/RTTI.html<br>http://www.linuxtopia.org/online_books/programming_books/c++_practical_programming/c++_practical_programming_208.html<br>http://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI<br>http://docs.hp.com/en/B3901-90024/ch06s05.html<br>各种cast：<br>http://www.codeproject.com/KB/mcpp/castingbasics.aspx<br>http://www.cplusplus.com/doc/tutorial/typecasting.html<br>http://blog.baisi.net/?441896/viewspace-4063<br>http://blog.csdn.net/singno116/archive/2008/04/18/2304962.aspx<br>http://blog.chinaunix.net/u2/70445/showart_1357610.html<br>http://read.newbooks.com.cn/info/50236.html</p>
<p>谢谢！</p>
</div>
<div id=MySignature>
<p>感谢，Thanks！<br><br>作者：<a href="http://itech.cnblogs.com/" target=_blank><u><font color=#0066cc>iTech</font></u></a><br>出处：<a href="http://itech.cnblogs.com/" target=_blank><u><font color=#0066cc>http://itech.cnblogs.com/</font></u></a> <br>本文版权归作者iTech所有，转载请包含作者签名和出处，不得用于商业用途，非则追究法律责任！</p>
</div>
<script type=text/javascript>
if ($ != jQuery) {
$ = jQuery.noConflict();
}
var isLogined = false;
var cb_blogId = 50245;
var cb_entryId = 1394268;
var cb_blogApp = "itech";
var cb_blogUserGuid = "caee16b0-1cf5-dd11-9e4d-001cf0cd104b";
var cb_entryCreatedDate = '2009/2/19 19:06:00';
</script>
<img src ="http://www.cppblog.com/yehao/aggbug/144143.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-04-13 18:29 <a href="http://www.cppblog.com/yehao/articles/144143.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>