﻿<?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++博客-OxFAN-随笔分类-C/C++语言</title><link>http://www.cppblog.com/ofan/category/10397.html</link><description>::Just For Fun::</description><language>zh-cn</language><lastBuildDate>Mon, 04 May 2009 09:13:09 GMT</lastBuildDate><pubDate>Mon, 04 May 2009 09:13:09 GMT</pubDate><ttl>60</ttl><item><title>C++虚函数表解析[转载]</title><link>http://www.cppblog.com/ofan/archive/2009/04/30/81537.html</link><dc:creator>OxFAN</dc:creator><author>OxFAN</author><pubDate>Thu, 30 Apr 2009 03:02:00 GMT</pubDate><guid>http://www.cppblog.com/ofan/archive/2009/04/30/81537.html</guid><wfw:comment>http://www.cppblog.com/ofan/comments/81537.html</wfw:comment><comments>http://www.cppblog.com/ofan/archive/2009/04/30/81537.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ofan/comments/commentRss/81537.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ofan/services/trackbacks/81537.html</trackback:ping><description><![CDATA[<span  style="border-collapse: collapse; color: rgb(51, 51, 51); font-family: verdana; font-size: 13px; line-height: 17px; -webkit-border-horizontal-spacing: 2px; -webkit-border-vertical-spacing: 2px; "><p>(注：个人觉得这篇文章不错故转载了)</p><p>&#160;&#160; &#160; C++中的虚函数的作用主要是实现了多态的机制。关于多态，简而言之就是用父类型别的指针指向其子类的实例，然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有&#8220;多种形态&#8221;，这是一种泛型技术。所谓泛型技术，说白了就是试图使用不变的代码来实现可变的算法。比如：模板技术，RTTI技术，虚函数技术，要么是试图做到在编译时决议，要么试图做到运行时决议。</p><p>关于虚函数的使用方法，我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中，我只想从虚函数的实现机制上面为大家 一个清晰的剖析。</p><p>当然，相同的文章在网上也出现过一些了，但我总感觉这些文章不是很容易阅读，大段大段的代码，没有图片，没有详细的说明，没有比较，没有举一反三。不利于学习和阅读，所以这是我想写下这篇文章的原因。也希望大家多给我提意见。</p><p>言归正传，让我们一起进入虚函数的世界。</p><h3 style="font-size: 13px; color: rgb(0, 0, 0); ">虚函数表</h3><p>对C++ 了解的人都应该知道虚函数（Virtual Function）是通过一张虚函数表（Virtual Table）来实现的。简称为V-Table。 在这个表中，主是要一个类的虚函数的地址表，这张表解决了继承、覆盖的问题，保证其容真实反应实际的函数。这样，在有虚函数的类的实例中这个表被分配在了 这个实例的内存中，所以，当我们用父类的指针来操作一个子类的时候，这张虚函数表就显得由为重要了，它就像一个地图一样，指明了实际所应该调用的函数。</p><p>这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到，编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置（这是为了保证正确取到虚函数的偏移量）。 这意味着我们通过对象实例的地址得到这张虚函数表，然后就可以遍历其中函数指针，并调用相应的函数。</p><p>听我扯了那么多，我可以感觉出来你现在可能比以前更加晕头转向了。 没关系，下面就是实际的例子，相信聪明的你一看就明白了。</p><p>假设我们有这样的一个类：</p><p>class Base {</p><p>public:</p><p>virtual void f() { cout &lt;&lt; "Base::f" &lt;&lt; endl; }</p><p>virtual void g() { cout &lt;&lt; "Base::g" &lt;&lt; endl; }</p><p>virtual void h() { cout &lt;&lt; "Base::h" &lt;&lt; endl; }</p><p>};</p><p>按照上面的说法，我们可以通过Base的实例来得到虚函数表。 下面是实际例程：</p><p>typedef void(*Fun)(void);</p><p>Base b;</p><p>Fun pFun = NULL;</p><p>cout &lt;&lt; "虚函数表地址：" &lt;&lt; (int*)(&amp;b) &lt;&lt; endl;</p><p>cout &lt;&lt; "虚函数表 — 第一个函数地址：" &lt;&lt; (int*)*(int*)(&amp;b) &lt;&lt; endl;</p><p>// Invoke the first virtual function</p><p>pFun = (Fun)*((int*)*(int*)(&amp;b));</p><p>pFun();</p><p>实际运行经果如下：(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)</p><p>虚函数表地址：0012FED4</p><p>虚函数表 — 第一个函数地址：0044F148</p><p>Base::f</p><p>通过这个示例，我们可以看到，我们可以通过强行把&amp;b转成int *，取得虚函数表的地址，然后，再次取址就可以得到第一个虚函数的地址了，也就是Base::f()，这在上面的程序中得到了验证（把int* 强制转成了函数指针）。通过这个示例，我们就可以知道如果要调用Base::g()和Base::h()，其代码如下：</p><p>(Fun)*((int*)*(int*)(&amp;b)+0); // Base::f()</p><p>(Fun)*((int*)*(int*)(&amp;b)+1); // Base::g()</p><p>(Fun)*((int*)*(int*)(&amp;b)+2); // Base::h()</p><p>这个时候你应该懂了吧。什么？还是有点晕。也是，这样的代码看着太乱了。没问题，让我画个图解释一下。如下所示：</p><p><img src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable1.jpg" border="0"></p><p>注意：在上面这个图中，我在虚函数表的最后多加了一个结点，这是虚函数表的结束结点，就像字符串的结束符&#8220;\0&#8221;一样，其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下，这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下，这个值是如果1，表示还有下一个虚函数表，如果值是0，表示是最后一个虚函数表。</p><p>下面，我将分别说明&#8220;无覆盖&#8221;和&#8220;有覆盖&#8221;时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况，主要目的是为了给一个对比。在比较之下，我们可以更加清楚地知道其内部的具体实现。</p><h3 style="font-size: 13px; color: rgb(0, 0, 0); ">一般继承（无虚函数覆盖）</h3><p>下面，再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系：</p><p><img src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_Drawing3.jpg" border="0"></p><p>请注意，在这个继承关系中，子类没有重载任何父类的函数。那么，在派生类的实例中，其虚函数表如下所示：</p><p>对于实例：Derive d; 的虚函数表如下：</p><p><img src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable2.JPG" border="0"></p><p>我们可以看到下面几点：</p><p>1）虚函数按照其声明顺序放于表中。</p><p>2）父类的虚函数在子类的虚函数前面。</p><p>我相信聪明的你一定可以参考前面的那个程序，来编写一段程序来验证。</p><h3 style="font-size: 13px; color: rgb(0, 0, 0); ">一般继承（有虚函数覆盖）</h3><p>覆盖父类的虚函数是很显然的事情，不然，虚函数就变得毫无意义。下面，我们来看一下，如果子类中有虚函数重载了父类的虚函数，会是一个什么样子？假设，我们有下面这样的一个继承关系。</p><p><img src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_Drawing4.jpg" border="0"></p><p>为了让大家看到被继承过后的效果，在这个类的设计中，我只覆盖了父类的一个函数：f()。那么，对于派生类的实例，其虚函数表会是下面的一个样子：</p><p><img src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable3.JPG" border="0"></p><p>我们从表中可以看到下面几点，</p><p>1）覆盖的f()函数被放到了虚表中原来父类虚函数的位置。</p><p>2）没有被覆盖的函数依旧。</p><p>这样，我们就可以看到对于下面这样的程序，</p><p>Base *b = new Derive();</p><p>b->f();</p><p>由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代，于是在实际调用发生时，是Derive::f()被调用了。这就实现了多态。</p><h3 style="font-size: 13px; color: rgb(0, 0, 0); ">多重继承（无虚函数覆盖）</h3><p>下面，再让我们来看看多重继承中的情况，假设有下面这样一个类的继承关系。注意：子类并没有覆盖父类的函数。</p><p><img src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_Drawing1.jpg" border="0"></p><p>对于子类实例中的虚函数表，是下面这个样子：</p><p><img src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable4.JPG" border="0"></p><p>我们可以看到：</p><p>1） 每个父类都有自己的虚表。</p><p>2） 子类的成员函数被放到了第一个父类的表中。（所谓的第一个父类是按照声明顺序来判断的）</p><p>这样做就是为了解决不同的父类类型的指针指向同一个子类实例，而能够调用到实际的函数。</p><h3 style="font-size: 13px; color: rgb(0, 0, 0); ">多重继承（有虚函数覆盖）</h3><p>下面我们再来看看，如果发生虚函数覆盖的情况。</p><p>下图中，我们在子类中覆盖了父类的f()函数。</p><p><img src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_Drawing2.jpg" border="0"></p><p>下面是对于子类实例中的虚函数表的图：</p><p><img src="http://p.blog.csdn.net/images/p_blog_csdn_net/haoel/15190/o_vtable5.jpg" border="0"></p><p>我们可以看见，三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样，我们就可以任一静态类型的父类来指向子类，并调用子类的f()了。如：</p><p>Derive d;</p><p>Base1 *b1 = &amp;d;</p><p>Base2 *b2 = &amp;d;</p><p>Base3 *b3 = &amp;d;</p><p>b1->f(); //Derive::f()</p><p>b2->f(); //Derive::f()</p><p>b3->f(); //Derive::f()</p><p>b1->g(); //Base1::g()</p><p>b2->g(); //Base2::g()</p><p>b3->g(); //Base3::g()</p><h3 style="font-size: 13px; color: rgb(0, 0, 0); ">安全性</h3><p>每次写C++的文章，总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述，相信我们对虚函数表有一个比较细致的了解了。水可载舟，亦可覆舟。下面，让我们来看看我们可以用虚函数表来干点什么坏事吧。</p><p><strong>一、通过父类型的指针访问子类自己的虚函数</strong></p><p>我们知道，子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数，但我们根本不可能使用下面的语句来调用子类的自有虚函数：</p><p>Base1 *b1 = new Derive();</p><p>b1->f1(); //编译出错</p><p>任何妄图使用父类指针想调用子类中的<strong>未覆盖父类的成员函数</strong>的行为都会被编译器视为非法，所以，这样的程序根本无法编译通过。但在运行时，我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。（关于这方面的尝试，通过阅读后面附录的代码，相信你可以做到这一点）</p><p><strong>二、访问non-public</strong><strong>的虚函数</strong></p><p>另外，如果父类的虚函数是private或是protected的，但这些非public的虚函数同样会存在于虚函数表中，所以，我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数，这是很容易做到的。</p><p>如：</p><p>class Base {</p><p>private:</p><p>virtual void f() { cout &lt;&lt; "Base::f" &lt;&lt; endl; }</p><p>};</p><p>class Derive : public Base{</p><p>};</p><p>typedef void(*Fun)(void);</p><p>void main() {</p><p>Derive d;</p><p>Fun pFun = (Fun)*((int*)*(int*)(&amp;d)+0);</p><p>pFun();</p><p>}</p><h3 style="font-size: 13px; color: rgb(0, 0, 0); ">结束语</h3><p>C++这门语言是一门Magic的语言，对于程序员来说，我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言，我们就必需要了解C++里面的那些东西，需要去了解C++中那些危险的东西。不然，这是一种搬起石头砸自己脚的编程语言。</p></span>
<img src ="http://www.cppblog.com/ofan/aggbug/81537.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ofan/" target="_blank">OxFAN</a> 2009-04-30 11:02 <a href="http://www.cppblog.com/ofan/archive/2009/04/30/81537.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>