虚表的形成
一切结论都必须以事实为依据,这样才能形成长久记忆!
虚表的形成过程:
一、对于非继承类而言:编译器会根据类中是否有虚函数产生虚表,如果有虚函数,则会形成虚表,虚表中会按照成员函数声明顺序存放函数的地址,从而形成存放函数入口地址的函数指针数组,最后把数组地址存放在类的开始的位置,只一个指针的大小。
二、对于继承类而言:对于单继承,如果父类中有虚函数表,则编译器编译时,会把父类的虚表赋值一份,并把新的地址放在类的开始位置,只占一个指针大小的空间;对于多继承,编译器会把多个父类的虚表分别复制一份,并把新的虚表地址依次按照继承顺序存放在子类的开始位置。
三、对于继承时,如果其重写其父类的虚函数(注意重写与重载的区别),编译器会把新的函数的地址更新到原来虚函数对应的位置,如果该类新增虚函数是,编译器会默认把新的虚函数的地址存放第一个虚表中,追加在其后。对于多重继承时,重写会覆盖所有继承虚表中被重写的虚函数地址;新增的虚函数会追加在第一个虚表的内虚表项的后边。
四、虚函数就是指成员函数前加virtual 关键字的函数,但构造函数和友元函数(不是类成员函数)不会是虚函数,其他成员函数都可以使用virtual 修饰,当然纯虚函数也是虚函数。
五、补充:在c++中纯虚函数与纯面向对象的语言,如java、C#,中的接口在功能上是一致的,都是起到接口规范的作用,只是在C++中纯虚函数在类中定义,所以导致在C++中,继承可以是多继承方式,而在纯面向对象中,只能是单继承。
对于以上的结论的图像化的表示,图形方式的表示更直观一点,大家可以参考http://blog.csdn.net/haoel/article/details/1948051。
但是关于这些论点的论证,希望大家参考我的代码。
"朝花夕拾",为了论证之前结论,自己再次验证一下:
注意:
①要确认当前的主机的位数以及编译器。64位机指针长度8byte,32位机指针长度为4byte.
②注意内存对齐机制,这个机制以为着,申明3byte内存的类大小:sizeof(类名) = 4.关于内存对齐和c语言结构体的对齐方式是一致的。
③空类的大小为1byte,原因是被编译器插进去的一个char ,使得这个class的不同实体(object)在内存中配置独一无二的地址,这样产生的对象的地址才不同。
④继承的时候,无论虚函数的的访问修饰符,和继承的方式如何都可以通过虚表进行调用。
⑤继承的时候,复制虚表本身,并没有复制虚表项中函数指针所指的函数的函数体,即虚函数在内存中的定义只有一份。
以下是验证内容:
结论一:
1 #include <iostream> 2 3 using namespace std; 4 5 class A 6 { 7 8 }; 9 class B 10 { 11 virtual void fun1() 12 { 13 cout <<"fun1" <<endl; 14 } 15 virtual void fun2() 16 { 17 cout <<"fun2" <<endl; 18 } 19 }; 20 21 int main() 22 { 23 //空类大小为1 24 cout << sizeof(A) <<endl; 25 //指针大小为系统位数 26 cout << sizeof(new A()) << endl; 27 A t_a; 28 //对象也是1 29 cout << sizeof(t_a) << endl; 30 31 32 cout << "******************" <<endl; 33 34 //两个虚函数,放在一个虚函数表里,最后将虚表的入口地址存放在类开始位置。 35 //指针大小为系统位数 36 cout << sizeof(B) <<endl; 37 //指针大小为系统位数 38 cout << sizeof(new B()) << endl; 39 B t_b; 40 //指针大小为系统位数 41 cout << sizeof(t_b) << endl; 42 43 cout << "*******************" << endl; 44 //指向对象的地址,指向对象的开始位置 45 int *p = (int *)&t_b; 46 //指向虚表的第一项 47 int *p_fun = (int *)*p; 48 //指向虚表的第二项, 49 //sizeof(void *)/sizeof(int) 而不使用1的原因是: 50 //在不同位数的系统中,指针的加减的单位是取决于其指针的类型 51 //如在32位系统中,int型指针+1,可以指向第二个指针。 52 //当时在64位系统中,如果+1,此时指针移动的是sizeof(int)byte的位置,此时指向的不是第二个指针的位置,因为指针大小为8byte。所以使用sizeof(void *)/sizeof(int) 获得每个指针占用几个单位的int型空间。 53 int *p_fun2 = p_fun + sizeof(void *)/sizeof(int); 54 //将两项内的数据,即函数指针,强转。得到函数指针 55 void (*f1)() = (void (*) (void))*p_fun; 56 void (*f2)() = (void (*) (void))*p_fun2; 57 //执行函数指针 58 f1(); 59 f2(); 60 //打印的时候,需要转换成int型指针才能正常打印 61 cout << (int *)f1 <<endl; 62 cout << (int *)f2 <<endl; 63 64 65 #if 0 66 //这种方式,编译器报错 67 cout << &(t_b.fun1) << endl; 68 69 #endif 70 71 return 0; 72 }
运行结果:
结论二:
1 #include <iostream> 2 3 using namespace std; 4 5 class A 6 { 7 public: 8 virtual void a1() 9 { 10 cout << "a1" <<endl; 11 } 12 virtual void a2() 13 { 14 cout << "a2" << endl; 15 } 16 }; 17 class AA 18 { 19 20 virtual void aa1() 21 { 22 cout << "aa1" <<endl; 23 } 24 virtual void aa2() 25 { 26 cout << "aa2" << endl; 27 } 28 }; 29 class B:public A 30 { 31 32 }; 33 34 class C:public A,public AA 35 { 36 37 }; 38 39 int main() 40 { 41 cout << "*****************单继承验证**********************"<<endl; 42 cout << sizeof(A) <<endl; 43 cout << sizeof(B) <<endl; 44 45 //证明①子类中的虚表与父类虚表入口地址不同 46 A t_a; 47 int * pa = (int *)&t_a; 48 int *pa1 = (int *)*pa; 49 cout << "父类虚表地址:" << pa1 <<endl; 50 51 B t_b; 52 int * pb = (int *)&t_b; 53 int * pb1 = (int *)*pb; 54 int * pb2 = pb1 + sizeof(void *)/sizeof(int); 55 cout << "子类虚表地址:" << pb1 << endl; 56 //结论①:结果地址不同。 57 58 //证明②: 子类中仍然可以通过虚函数调用父类的方法, 59 void (*f1)() = (void (*) (void))*pb1; 60 void (*f2)() = (void (*) (void))*pb2; 61 62 f1(); 63 f2(); 64 //结论②:子类可以通过新的虚表访问父类的虚函数 65 66 //最终的结论:单继承的时候,是在内存中复制一份虚表,并将新的地址放在子类开始位置 67 68 cout << "****************多继承验证*********************" <<endl; 69 //对于多继承,编译器会把多个父类的虚表分别复制一份,并把新的虚表地址依次按照继承顺序存放在子类的开始位置。 70 AA t_aa; 71 int *paa = (int *)&t_aa; 72 int *paa1 = (int *)*paa; 73 74 cout << "父类A的虚表地址:" << pa1 << endl; 75 cout << "父类AA的虚表地址:" << paa1 << endl; 76 77 C t_c; 78 int *pc1 = (int *)&t_c; 79 //继承A虚表的入口地址 80 int *pc1_1 = (int *)*pc1; 81 //继承AA虚表的入口地址 82 int *pc2 = pc1 + sizeof(void *)/sizeof(int); 83 int *pc2_1 = (int*)*pc2; 84 85 cout << "子类中获得第一项虚表地址:" << pc1_1 << endl; 86 cout << "子类中获得第二项虚表地址:" << pc2_1 << endl; 87 88 //验证上述地址确实是copy后的新的虚表地址 89 cout << "通过继承子类A的虚表执行虚函数" <<endl; 90 int *pc1_2 = pc1_1 + sizeof(void *)/sizeof(int); 91 void (*fc1_1)() = (void (*) (void))*pc1_1; 92 void (*fc1_2)() = (void (*) (void))*pc1_2; 93 94 fc1_1(); 95 fc1_2(); 96 97 98 cout << "通过继承子类AA的虚表执行虚函数" <<endl; 99 int *pc2_2 = pc2_1 + sizeof(void *)/sizeof(int); 100 void (*fc2_1)() = (void (*) (void))*pc2_1; 101 void (*fc2_2)() = (void (*) (void))*pc2_2; 102 103 fc2_1(); 104 fc2_2(); 105 106 return 0; 107 }
运行截图:
结论三:
1 #include <iostream> 2 3 using namespace std; 4 5 class A 6 { 7 public: 8 virtual void a1() 9 { 10 cout << "a1" <<endl; 11 } 12 virtual void a2() 13 { 14 cout << "a2" << endl; 15 } 16 }; 17 class AA 18 { 19 20 virtual void a1() 21 { 22 cout << "aa1" <<endl; 23 } 24 virtual void aa2() 25 { 26 cout << "aa2" << endl; 27 } 28 }; 29 class B:public A 30 { 31 32 }; 33 34 class BB:public A 35 { 36 //重写A的虚函数,注意不是重载,重载不是一个函数,只是函数名相同罢了 37 virtual void a1() 38 { 39 cout << "bb1" <<endl; 40 } 41 virtual void a2() 42 { 43 cout << "bb2" << endl; 44 } 45 //新增虚函数的时候,会将新增的虚函数地址作为新的表项,追加在原虚表的后边 46 virtual void b3() 47 { 48 cout << "bb3" << endl; 49 } 50 }; 51 52 class C:public A,public AA 53 { 54 //重写A的虚函数,注意不是重载,重载不是一个函数,只是函数名相同罢了 55 virtual void a1() 56 { 57 cout << "cc1" <<endl; 58 } 59 virtual void a2() 60 { 61 cout << "cc2" << endl; 62 } 63 //新增虚函数的时候,会将新增的虚函数地址作为新的表项,追加在原虚表的后边 64 virtual void b3() 65 { 66 cout << "cc3" << endl; 67 } 68 }; 69 70 71 int main() 72 { 73 A t_a; 74 int * pa = (int *)&t_a; 75 int *pa1 = (int *)*pa; 76 //cout << "父类虚表地址:" << pa1 <<endl; 77 78 B t_b; 79 int * pb = (int *)&t_b; 80 int * pb1 = (int *)*pb; 81 int * pb2 = pb1 + sizeof(void *)/sizeof(int); 82 //cout << "子类虚表地址:" << pb1 << endl; 83 84 void (*f1)() = (void (*) (void))*pb1; 85 void (*f2)() = (void (*) (void))*pb2; 86 87 f1(); 88 f2(); 89 //结论②:子类可以通过新的虚表访问父类的虚函数 90 91 92 BB t_bb; 93 int * pbb = (int *)&t_bb; 94 int * pbb1 = (int *)*pbb; 95 int * pbb2 = pbb1 + sizeof(void *)/sizeof(int); 96 int * pbb3 = pbb2 + sizeof(void *)/sizeof(int); 97 //cout << "子类虚表地址:" << pb1 << endl; 98 99 void (*fb1)() = (void (*) (void))*pbb1; 100 void (*fb2)() = (void (*) (void))*pbb2; 101 void (*fb3)() = (void (*) (void))*pbb3; 102 cout << "***********************单继承的情况****************************" << endl; 103 104 cout << "验证重写的时候,覆盖原虚表中被重写的虚函数的入口地址" << endl; 105 fb1(); 106 fb2(); 107 cout << "验证新增虚函数的时候,追加在虚表已有表项的后边" << endl; 108 fb3(); 109 110 111 cout << "***********************多继承的情况****************************" << endl; 112 //验证多重继承的时候,重写的虚函数地址会覆盖所有虚表中中被重写的虚函数地址, 113 //验证多重继承的时候,新增虚函数会自动追加到第一个继承虚表的后边。不会再追加到继承的其他虚表的虚表项。 114 115 C t_c; 116 //虚表1 117 int * pc1 = (int *)&t_c; 118 //虚表1的表项1 119 int * pc1_1 = (int *)*pc1; 120 //虚表1的表项2 121 int * pc1_2 = pc1_1 + sizeof(void *)/sizeof(int); 122 //虚表1的表项3 123 int * pc1_3 = pc1_2 + sizeof(void *)/sizeof(int); 124 125 //虚表2 126 int * pc2 = pc1 + sizeof(void *)/sizeof(int); 127 //虚表2的表项1 128 int * pc2_1 = (int *)*pc2; 129 //虚表2的表项2 130 int * pc2_2 = pc2_1 + sizeof(void *)/sizeof(int); 131 //虚表2的表项3 132 int * pc2_3 = pc2_2 + sizeof(void *)/sizeof(int); 133 134 //重写A的虚函数1 135 void (*fc1_1)() = (void (*) (void))*pc1_1; 136 //重写A的虚函数2 137 void (*fc1_2)() = (void (*) (void))*pc1_2; 138 //追加新的虚函数3 139 void (*fc1_3)() = (void (*) (void))*pc1_3; 140 141 142 //重写AA的虚函数1,因为A和AA的第一个虚函数是相同的 143 void (*fc2_1)() = (void (*) (void))*pc2_1; 144 //继承AA的虚函数2 145 void (*fc2_2)() = (void (*) (void))*pc2_2; 146 //追加新的虚函数3,段错误,只追加到第一个虚表的后边,当前是虚表2,没有追加,如果访问就段错误 147 void (*fc2_3)() = (void (*) (void))*pc2_3; 148 149 cout << "重写A的虚函数1" << endl; 150 fc1_1(); 151 cout << "重写A的虚函数2" << endl; 152 fc1_2(); 153 cout << "追加虚表1的虚函数" << endl; 154 fc1_3(); 155 156 157 cout << "重写AA的虚函数1" << endl; 158 fc2_1(); 159 cout << "继承AA的虚函数2" << endl; 160 fc2_2(); 161 //cout << "追加虚表1的虚函数" << endl; 162 //fc2_3(); 163 164 return 0; 165 }
运行截图: