C++ 虚函数剖析
2016-11-18 15:07
169 查看
虚函数之虚表
带有虚函数的类的对象模型剖析
(编译环境Vs2013)看到这个题目,很多的人可能很想知道什么是虚表????为什么要说这个东西呢???这个虚表有什么作用呢????
当然,先容我买个关子,后面再说,,,,现在我们先来看看带有虚函数类的结构剖析:::
大家来看看一段代码:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; class B { public: B(int b = 0 ) :_b(b) { cout << "B::B(int b = 0 )" << endl; } void Test1() { cout << "void B::Test1()" << endl; } virtual void Test2() { cout << "void B::Test2()" << endl; } virtual void Test3() { cout << "void B::Test3()" << endl; } virtual void Test4() { cout << "void B::Test4()" << endl; } private: int _b; }; int main() { B b(1); int a = sizeof(b) ; cout << a << endl; return 0; }
这段代码中定义了一个B类类中 包含 成员变量_b ,1个成员函数Test1 ,3个虚函数Test2、Test3、Test4
代码输出后结果得到:
可以看到得到的B类对象的大小为 8,按照之前的类的大小计算,只应包含一个成员变量的大小为4,为什么还多出了4字节呢????
那就然我们来看看编译器在内存中的存储吧!!!!!!!!!!!!
从编译器的内存调试窗口中 ,我们可以明显看出 B类对象b包含两部分:
一部分呢是 一串数字(猜测可能是存了个地址)
另一部分呢 就是B类对象的成员变量 在内存保存的是给的1 ;;;;
第一部分看上去是个指针 ,那我们就当他们是指针 ,里面保存的是地址 ,,,
如果保存的是地址,那么这个 地址里保存的是什么呢?????
查看这段地址后 ,得到了这幅图;;;;;
在这段地址里,保存了有四部分,前三部分像是地址 ,第四部分是 0 ;
正好在这个类中有三个虚函数,我们这三个地址保存的是三个虚函数的地址,0表示结束。。。
既然假设了之后 ,,,,我们就 得验证一下,但要怎么验证呢?????
其实我们可通过反证法来试试,,,,我们就当它们保存的是虚函数,那么我们只需定义一个函数指针
用这三个地址来调用,看看结果,,,就知道是不是代表的是虚函数的地址》》》》》》
代码如下:
typedef void(*ptest)();//定义个函数指针ptest int main() { B b(1); ptest *p = (ptest*)(*((int*)(&b)));//p表示的是指向函数指针的指针; while (*p)//当遇到我们在内存中看到的数字0 { (*p)(); p++; } return 0; }最后输出的结果输出为
通过输出的结构我们可以得出结论:这些地址表示的就是虚函数的地址。。。。。
在此得出结论后 ,我们基本了解到
带有虚函数的类在构造函数时,,,会先为对象空间中首位放上一个指针, 内部存储的是 一个表格
这个表格 内部存储的是类中虚函数的地址 ,并且在结束时,保存一个0 ;;;;
我们把这个表叫做是 虚表
那么开头遇到的问题是不是就解决了
带有虚函数的继承的对象模型剖析
上面我们基本了解到了什么是虚表,虚表要怎么存储????但是上面的知识基础知识 ,,,,,
我们都知道,虚函数是用来实现多态的,多态肯定要会包含到继承,,,,
那么,接下来我们就来看看,在继承中中的虚表存储
单继承虚表
我们先从简单的单继承来说起!!!!!!基类对象的结构剖析,我们之前也了解到了,那么我们现在主要说说派生类中的虚表。先看一段代码:
class B { public: B(int b = 0) :_b(b) {} void Test1() { cout << "void B::Test1()" << endl; } virtual void Test2() { cout << "void B::Test2()" << endl; } virtual void Test3() { cout << "void B::Test3()" << endl; } virtual void Test4() { cout << "void B::Test4()" << endl; } private: int _b; }; class D : public B { public: D(int b = 10, int d = 20) :B(b) , _d(d) {} virtual void Test2() { cout << "void D::Test2()" << endl; } virtual void Test3() { cout << "void D::Test3()" << endl; } virtual void Test5() { cout << "void D::Test5()" << endl; } private: int _d ; }; int main() { B b(1); D d; cout << sizeof(d) << endl; return 0; }这段代码中,对于类D继承B类 ,,,并且对于基类中 的虚函数Test2 ,Test3进行了重写。,
又加了一个虚函数Test5
最后得到的类D的对象的大小为12 ;; ; ;
D类对象在内存中的存储为:
从图中可以看出 对象中包含了虚表的地址0x0113dd6c 还有 基类 ,派生类自己的成员变量
接下来我们看看这个虚表和我们在基类中看到的有什么不同!!!!!!
虚表中明显多了一个地址, 那么多的这个地址就只会是Test5的地址了》》
通过函数指针来,调用这些函数看看》》》》
typedef void(*ptest)();//定义个函数指针ptest int main() { B b(1); D d; ptest *p = (ptest*)(*((int*)(&d)));//p表示的是指向函数指针的指针; while (*p)//当遇到我们在内存中看到的数字0 { (*p)(); p++; } return 0;生成的结果为::::
从图上我们可以看出,
Test2 ,Test3 调用的是派生类自己的, Test4调用的任然是基类的, Test5只能调用派生类的。。。。。
从中我们可以知道,派生类的在构造对象时,先调用基类的构造函数,
然后对于虚表进行了一定程度上的修改。。。
多继承虚表
上面我们初步了解到了在继承中派生类对象虚表的模型 , ,, ,下面我们来看看多继承的虚表的结构。。。下面的代码就是说多继承的虚表
class B1 { public: B1(int b = 0) :_b1(b) {} virtual void Test2() { cout << "void B1::Test2()" << endl; } virtual void Test4() { cout << "void B1::Test4()" << endl; } private: int _b1; }; class B2 { public: B2(int b2 = 2) :_b2(b2) {} virtual void Test3() { cout << "void B2::Test3()" << endl; } virtual void Test5() { cout << "void B2::Test5()" << endl; } private: int _b2; }; class D : public B1,public B2 { public: D(int b1 = 10,int b2 = 20, int d = 30) :B1(b1) ,B2(b2) , _d(d) {} virtual void Test2() { cout << "void D::Test2()" << endl; } virtual void Test5() { cout << "void D::Test5()" << endl; } virtual void Test6() { cout << "void D::Test6()" << endl; } private: int _d ; }; int main() { D d; return 0; }
简单介绍基类 B1, B2,类 派生类D
B1 :: 成员变量_b1; 虚函数Test2,Test3
B2 :: 成员变量_b2; 虚函数Test4,Test5
D::: 成员变量 _d;; 对基类B1的Test2 ,基类B2的Test5 进行重写 ;;;添加派生类自己的虚函数Test6
最后生成的D类对象 ——d的内存为
在图中我们可以大致将它分为三部分:::::
1、 B1类对象 (B1的虚表, B1的成员)
2、 B2类对象 (B2的虚表, B2的成员)
3、 D 类对象
我们可以调用这两个虚表来看看内部保存的虚函数
调用这些函数地址:
typedef void(*ptest)();//定义个函数指针ptest int main() { D d; ptest *p1 = (ptest*)(*((int*)(&d)));//p表示的是指向函数指针的指针; while (*p1)//当遇到我们在内存中看到的数字0 { (*p1)(); p1++; } cout << endl; ptest *p2 = (ptest*)*(((int*)(&d)) + 2);//p表示的是指向函数指针的指针; while (*p2)//当遇到我们在内存中看到的数字0 { (*p2)(); p2++; } return 0; }
生成的结果:
通过这个结果我们可以得出结论:
派生类对象在构造时》》》》》》 ,先构造 两个基类 ,然后依次对每个基类的虚表进行改写 , ,, , ,
关于派生类自己的虚函数地址,,,,,一般放在继承的基类的虚表中。 。 。。 。
菱形继承对象模型剖析
说到菱形继承 , , 一般都离不开虚拟继承的。。。。。如果要是不知什么是虚继承的话 , , ,可以看这篇博客C++之虚拟继承
为什么要单独说这个 菱形继承呢 ???
主要是因为,在虚拟继承时生成的类会在类对象之前也加上一个指针。。。。内部存的是
1、对象首位置到指针的偏移。
2、基类对象到指针的偏移。。。。
下面是一段带有虚函数的菱形继承(虚继承)的代码;;;;
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; class B { public: B(int b = 1) :_b(b) {} virtual void Test1() { cout << "void B::Test1()" << endl; } virtual void Test2() { cout << "void B::Test2()" << endl; } private: int _b; }; class C1 :virtual public B { public: C1(int b = 1,int c1= 2) :B(b) , _c1(c1) {} virtual void Test2() { cout << "void C1::Test2()" << endl; } private: int _c1; }; class C2 :virtual public B { public: C2(int b = 1, int c2 = 3) :B(b) , _c2(c2) {} virtual void Test1() { cout << "void C2::Test1()" << endl; } private: int _c2; }; class D :public C1, public C2 { public: D(int d = 4) :C1() ,C2() , _d(d) {} virtual void Test1() { cout << "void D::Test1()" << endl; } virtual void Test2() { cout << "void D::Test2()" << endl; } private: int _d; }; typedef void (*ptest)(); int main() { C1 c1; C2 c2; D d; ptest *p1 = (ptest *)*((int *)(&c1)+3); while (*p1) { (*p1)(); p1++; } ptest *p2 = (ptest *)*((int *)(&c2) + 3); while (*p2) { (*p2)(); p2++; } ptest *p3 = (ptest *)*((int *)(&d) + 6); while (*p3) { (*p3)(); p3++; } cout << sizeof(c1) << endl; cout << sizeof(c2) << endl; cout << sizeof(d) << endl; return 0; }代码简要说明:
B类 :成员变量 _b,,,带有虚函数Test1,Test2;
C1类(继承B类):成员变量_c1,对Test2,进行重写;;;
C2类(继承B类):成员变量_c2,对Test1,进行重写;;i,;
D类(多继承C1,C2类):成员变量_d,对Test1,Test2进行重写;;;;
根据代码我们生成的类的大小分别为
C1 20;
C2 20;
D 32;
在内存中对象的存储分别为:
C1 与C2 对象
从图中我们可以看出两个对象可以分为5个部分:
1、一个指针;;
2、C1 、C2类自己的成员;
3、一个0;
4、一个地址;
5、基类的成员;;;;
我们以C1对象为例,来看看上面的两个地址表示的到底是什么????
第一个地址:
第二个地址:
从两幅图中我们可以得出结论:
第一个地址表示的是虚拟继承得到的地址;
第二个地址表示的是虚函数的虚表的地址。
因此我们 可以大致猜测得到一个结论::::
(带有虚函数的)菱形继承的C1,与 C类的结构模型大致为:::::
那么再来看看D类对象的成员分布:
D类是一个继承两个子类,,,,
内存中的分布为
从图中类可以分为六部分:
1、C1的成员;
2、C2的成员;
3、D类自己的成员;
4、00 00 00 00
5、指针;
6、B类的成员。。。。。
我们从中可以得知的是上面的指针表示的是虚表的地址
因此我们可以的得到,菱形继承生成的D类的模型结构为:::::
这个图是相对于这段代码来写的;;;;;;
关于在内存中保存的 00 00 00 00 到底表示的是什么意思????
就留给大家下面来探讨吧!!!!!!!!
代码最后生成的结果::::
相关文章推荐
- 从汇编层面深度剖析C++虚函数
- 从汇编层面深度剖析C++虚函数
- c++ 虚函数实现原理简单剖析
- [c++深度剖析】继承和虚函数(一)
- 从汇编层面深度剖析 C++ 虚函数
- 从汇编层面深度剖析C++虚函数
- [置顶] 【C/C++学习】之十三、虚函数剖析
- 从汇编层面深度剖析C++虚函数
- C++之剖析虚函数1——虚函数的设计目的
- 基于LINUX平台G++编译器从汇编层面深度剖析C++虚函数
- 【C/C++学习】之十三、虚函数剖析
- 深度剖析C++虚函数
- 从汇编层面深度剖析c++虚函数
- 从汇编层面深度剖析C++虚函数
- 【深入剖析C++系列】虚函数的实现机制
- [置顶] 【C/C++学习】之十三、虚函数剖析
- 从汇编层面深度剖析C++虚函数 .
- C++ 虚函数 剖析
- 从汇编层面深度剖析C++虚函数
- C/C++程序员应聘常见面试题深入剖析