C++中的虚函数(表)实现机制以及用C语言对其进行的模拟实现(一)

2015-01-27 14:03:23 · 作者: · 浏览: 68
前言
?
? ? 大家都应该知道C++的精髓是虚函数吧? 虚函数带来的好处就是: 可以定义一个基类的指针, 其指向一个继承类, 当通过基类的指针去调用函数时, 可以在运行时决定该调用基类的函数还是继承类的函数. 虚函数是实现多态(动态绑定)/接口函数的基础. 可以说: 没有虚函数, C++将变得一无是处!
?
? ? 既然是C++的精髓, 那么我们有必要了解一下她的实现方式吗? 有必要! 既然C++是从C语言的基础上发展而来的, 那么我们可以尝试用C语言来模拟实现吗? 有可能! 接下来, 就是我一步一步地来解析C++的虚函数的实现方式, 以及用C语言对其进行的模拟.
?
C++对象的内存布局
?
? ? ?要想知道C++对象的内存布局, 可以有多种方式, 比如:
? ? ? ? 1. 输出成员变量的偏移, 通过offsetof宏来得到
? ? ? ? 2. 通过调试器查看, 比如常用的VS
?
? ? 1. 只有数据成员的对象
?
? ? 类实现如下:
?
1 class Base1
2 {
3 public:
4 ? ? int base1_1;
5 ? ? int base1_2;
6 };
? ? 对象大小及偏移:
?
sizeof(Base1) 8
offsetof(Base1, base1_1) 0
offsetof(Base1, base1_2) 4
?
?
?
?
?
? ? 可知对象布局:
? ? ? ? ?
? ? 可以看到, 成员变量是按照定义的顺序来保存的, 最先声明的在最上边, 然后依次保存!
? ? 类对象的大小就是所有成员变量大小之和.
?
? ? 2. 没有虚函数的对象
?
? ? 类实现如下:
?
复制代码
1 class Base1
2 {
3 public:
4 ? ? int base1_1;
5 ? ? int base1_2;
6?
7 ? ? void foo(){}
8 };
复制代码
? ? 结果如下:
?
sizeof(Base1) 8
offsetof(Base1, base1_1) 0
offsetof(Base1, base1_2) 4
?
?
?
?
?
?
? ? 和前面的结果是一样的? 不需要有什么疑问对吧?
? ? 因为如果一个函数不是虚函数, 那么他就不可能会发生动态绑定, 也就不会对对象的布局造成任何影响.?
? ? 当调用一个非虚函数时, 那么调用的一定就是当前指针类型拥有的那个成员函数. 这种调用机制在编译时期就确定下来了.
?
? ? 3. 拥有仅一个虚函数的类对象
?
? ? 类实现如下:
?
复制代码
1 class Base1
2 {
3 public:
4 ? ? int base1_1;
5 ? ? int base1_2;
6?
7 ? ? virtual void base1_fun1() {}
8 };
复制代码
? ? 结果如下:
?
sizeof(Base1) 12
offsetof(Base1, base1_1) 4
offsetof(Base1, base1_2) 8
?
?
?
?
?
?
? ? 咦? 多了4个字节? 且 base1_1 和 base1_2 的偏移都各自向后多了4个字节!
? ? 说明类对象的最前面被多加了4个字节的"东东", what's it?
?
? ? 现在, 我们通过VS2013来瞧瞧类Base1的变量b1的内存布局情况:
? ? ? ? ? ? ?(由于我没有写构造函数, 所以变量的数据没有根据, 但虚函数是编译器为我们构造的, 数据正确!)
? ? ? ? ? ? ?(Debug模式下, 未初始化的变量值为0xCCCCCCCC, 即:-858983460)
? ? ? ? ??
?
? ? 看到没? base1_1前面多了一个变量 __vfptr(常说的虚函数表vtable指针), 其类型为void**, ?这说明它是一个void*指针(注意:不是数组).
? ? 再看看[0]元素, 其类型为void*, 其值为 ConsoleApplication2.exe!Base1::base1_fun1(void), 这是什么意思呢? 如果对WinDbg比较熟悉, 那么应该知道这是一种惯用表示手法, 她就是指 Base1::base1_fun1() 函数的地址.?
?
? ? 可得, __vfptr的定义伪代码大概如下:
?
1 void* ? __fun[1] = { &Base1::base1_fun1 };
2 const void** ?__vfptr = &__fun[0];
?
?
? ? 值得注意的是:
? ? ? ? 1. 上面只是一种伪代码方式, 语法不一定能通过.
? ? ? ? 2. 该类的对象大小为12个字节, 大小及偏移信息如下:
?
sizeof(Base1) 12
offsetof(__vfptr) 0
offsetof(base1_1) 4
offsetof(base1_2) 8
?
?
?
?
?
? ? ? ?3. 大家有没有留意这个__vfptr? 为什么它被定义成一个指向指针数组的指针, 而不是直接定义成一个指针数组呢?
? ? ? ? ? ?我为什么要提这样一个问题? 因为如果仅是一个指针的情况, 您就无法轻易地修改那个数组里面的内容, 因为她并不属于类对象的一部分.
? ? ? ? ? ?属于类对象的, 仅是一个指向虚函数表的一个指针__vfptr而已, 下一节我们将继续讨论这个问题.
? ? ? ?4. 注意到__vfptr前面的const修饰. 她修饰的是那个虚函数表, 而不是__vfptr.
?
? ? 现在的对象布局如下:
? ? ? ? ??
?
? ? ? ?4. 虚函数指针__vfptr位于所有的成员变量之前定义.
?
?
? ? 注意到: 我并未在此说明__vfptr的具体指向, 只是说明了现在类对象的布局情况.
? ? 接下来看一个稍微复杂一点的情况, 我将清楚地描述虚函数表的构成.
?
? ? 4.拥有多个虚函数的类对象
?
? ? 和前面一个例子差不多, 只是再加了一个虚函数. 定义如下:
?
复制代码
1 class Base1
2 {
3 public:
4 ? ? int base1_1;
5 ? ? int base1_2;
6?
7 ? ? virtual void base1_fun1() {}
8 ? ? virtual void base1_fun2() {}
9 };
复制代码
? ? 大小以及偏移信息如下:
? ? ? ? ?
?
? ? 有情况!? 多了一个虚函数, 类对象大小却依然是12个字节!
?
? ? 再来看看VS形象的表现:
? ? ? ? ? ??
?
? ? 呀, __vfptr所指向的函数指针数组中出现了第2个元素, 其值为Base1类的第2个虚函数base1_fun2()的函数地址.
?
? ? 现在, 虚函数指针以及虚函数表的伪定义大概如下:
?
1 void* __fun[] = { &Base1::base1_fun1, &Base1::base1_fun