今天看了两篇虚函数的文章,有点体会,总结下。
理解虚函数( virtual function )的几个关键点:
1. 理解早绑定(early
binding)、晚绑定(late binding)。所谓early binding:即编译时就晓得了确定的函数地址;所谓late binding:On compile time,对函数(虚函数)的调用被搞成了:pObj->_vptr->vtable[],从而导致不到runtime,完全不知道实际函数地址。直到程序运行时,执行到这里,去vtable里拿到函数地址,才晓得。其实,原理很简单,只是单看这些名词的话会觉得好像很magic一样。
2. 理解虚函数赖以生存的底层机制:vptr + vtable。虚函数的运行时实现采用了VPTR/VTBL的形式,这项技术的基础:
①编译器在后台为每个包含虚函数的类产生一个静态函数指针数组(虚函数表),在这个类或者它的基类中定义的每一个虚函数都有一个相应的函数指针。
②每个包含虚函数的类的每一个实例包含一个不可见的数据成员vptr(虚函数指针),这个指针被构造函数自动初始化,指向类的vtbl(虚函数表)
③当客户调用虚函数的时候,编译器产生代码反指向到vptr,索引到vtbl中,然后在指定的位置上找到函数指针,并发出调用。
#include <stdio.h>
#include <stdlib.h>
#define Print(x) \
do { printf("%s = %p\n", #x, x); }while(false);
class A
{
public:
A() {}
virtual ~A() {}
public:
virtual void f() { printf("A::f()\n"); }
virtual void g() { printf("A::g()\n"); }
virtual void h() { printf("A::h()\n"); }
};
class B : public A
{
public:
B():A() {}
virtual ~B() {}
public:
virtual void i() { printf("B::i()\n"); }
virtual void g() { printf("B::g()\n"); }
virtual void f() { printf("B::f()\n"); }
virtual void j() { printf("B::j()\n"); }
};
void test(A*) { printf("It's just a test!\n"); }
/*
* 对于类的非静态函数,
* 编译器都会默认给其传个对象的this指针,
* 如果类的非静态函数指针定义少了对应类指针的参数,
* 通过函数指针调用,
* 函数的行为是不确定的。
*/
typedef void(*Fun)(A*);
int main(int argc, char **argv)
{
A a;
B b;
int* p = (*(int**)&a);
printf("\nclass A virtual function table address:\n");
Print(p);
Print((void*)p[0]);
Print((void*)p[1]);
Print((void*)p[2]);
Print((void*)p[3]);
Print((void*)p[4]);
p = (*(int**)&b);
printf("\nclass B virtual function table address:\n");
Print(p);
Print((void*)p[0]);
Print((void*)p[1]);
Print((void*)p[2]);
Print((void*)p[3]);
Print((void*)p[4]);
Print((void*)p[5]);
Print((void*)p[6]);
printf("\nclass A virtual function call:\n");
Fun pFun = (Fun)((*(int**)(&a))[2]);
pFun(&a);
pFun = (Fun)((*(int**)(&a))[3]);
pFun(&a);
pFun = (Fun)((*(int**)(&a))[4]);
pFun(&a);
printf("\nclass B virtual function call:\n");
pFun = (Fun)((*(int**)(&b))[2]);
pFun(&b);
pFun = (Fun)((*(int**)(&b))[3]);
pFun(&b);
pFun = (Fun)((*(int**)(&b))[4]);
pFun(&b);
pFun = (Fun)((*(int**)(&b))[5]);
pFun(&b);
pFun = (Fun)((*(int**)(&b))[6]);
pFun(&b);
//对虚函数表的修改是禁止的,如修改字符串常量
//*(int**)(&b)[2] = (int*)test; //error
return 0;
}
|
输出:
p = 0x8048cd0 (void*)p[0] = 0x8048a88 (void*)p[1] = 0x80489d8 (void*)p[2] = 0x8048974 (void*)p[3] = 0x8048960 (void*)p[4] = 0x804894c
class B virtual function table address: p = 0x8048d08 (void*)p[0] = 0x8048a56 (void*)p[1] = 0x8048a24 (void*)p[2] = 0x80489c4 (void*)p[3] = 0x80489b0 (void*)p[4] = 0x804894c (void*)p[5] = 0x804899c (void*)p[6] = 0x8048988
class A virtual function call: A::f() A::g() A::h()
class B virtual function call: B::f() B::g() A::h() B::i() B::j()
|
从输出可以看出:
1.从父类集成下来的虚函数指针在虚函数表位置由父类定义顺序决定;
2.子类中增加的虚函数指针按定义顺序添加到虚函数表的尾部;
3.vtbl[0]存放对应类的析构函数函数指针,vtbl[1]存放type_info,用于RTTI。
阅读(1666) | 评论(1) | 转发(0) |