C++虚表的原理
Posted liangyc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++虚表的原理相关的知识,希望对你有一定的参考价值。
下面这篇文章讲的很好。
http://www.cnblogs.com/lihaosky/articles/1606502.html
假设我们有这样的一个类:
class Base
public:
virtual void f() cout << "Base::f" <<>
virtual void g() cout << "Base::g" <<>
virtual void h() cout << "Base::h" <<>
;
按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
cout << "虚函数表地址:" << (int*)(&b) <<>
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) <<>
// Invoke the first virtual function
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)
虚函数表地址:0012FED4
虚函数表 — 第一个函数地址:0044F148
Base::f
通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:
(Fun)*((int*)*(int*)(&b)+0); // Base::f()
(Fun)*((int*)*(int*)(&b)+1); // Base::g()
(Fun)*((int*)*(int*)(&b)+2); // Base::h()
*注:事实上上面的代码在我的机子上并不能打印出 Base::g() 和 Base::h()。发现这个函数的指针的大小是8bytes,所以给int指针加一只加了4个bytes,所以打印出来了segmentation fault,所以最好先得到Fun的大小然后根据他来加,在我的机子上给加2就可以正确打出g和h了。
一般继承(无虚函数覆盖)
一般继承(有虚函数覆盖)
多重继承(无虚函数覆盖)
我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
多重继承(有虚函数覆盖)
看看我们可以用虚函数表来干点什么坏事吧。
一、通过父类型的指针访问子类自己的虚函数
我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:
Base1 *b1 = new Derive();
b1->f1(); //编译出错
任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)
二、访问non-public的虚函数
另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。
如:
class Base
private:
virtual void f() cout << "Base::f" <<>
;
class Derive : public Base
;
typedef void(*Fun)(void);
void main()
Derive d;
Fun pFun = (Fun)*((int*)*(int*)(&d)+0);
pFun();
以上是关于C++虚表的原理的主要内容,如果未能解决你的问题,请参考以下文章