C++ 对象的内存布局
2015-11-18 11:34
525 查看
这里的例子全部来自陈皓的 C++ 对象的内存布局(上),经过修改的。而C++ 对象的内存布局(下)看了没有什么问题的样子,没有时间测试了。
编译器:g++ (Ubuntu 4.9.2-10ubuntu13) 4.9.2
环境:ubuntu 15.04 64位系统(地址占8字节)
例子1:
单一的一般继承,所有函数均是virtual,每个类中均有1个long long变量:
输出:
、
解释:继承关系为 Parent<--Child<--GrandChild,其中Child是中间类,覆盖了Parent中一个函数f。GrandChild是最底派生类,覆盖了Parent和Child的函数f,再覆盖Child中的函数g_child。
总结:GrandChild的对象gc一共占4*8=32个字节。其中首项是虚函数表指针,剩下的3项是3个类中的成员变量,按继承的声明顺序排列。虚函数表中有6个函数,其中来自GrandChild的有3个,Child的1个,Parent的2个。GrandChild的3个必定不会少,而且前两个函数f和g_child就覆盖掉所有基类中同名的函数。Parent中剩下2个没有被覆盖的,而f不幸被覆盖。Child中只剩下1个了,前两个都被覆盖了。
其实可以先看两层的Parent<--Child,而GrandChild先不看。那么派生类覆盖掉基类1个函数,剩下的就是2+3=5个函数。来了新的基类GrandChild后,也新来3个函数,但是其中2个已经存在,先覆盖,剩下一个直接加进去,即5+1=6个函数。
验证了如下这副图:
例子2:
多重继承,基类与派生类中有同名虚函数,每个类中有自己的1个变量。
输出:
解释:代码中只测试了派生类对象d的4*8=32个字节的内容(不连续的)。其他的推测一下就行了。对于[0][4]和[2][4]其实是一个标志,1表示后面没有指针了,是编译器做的手脚(上一篇中讲了无法验证,在这验证了)。
总结:对象d中分4个部分。
第一部分是基类Base1的,共2*8=16个字节,前8个字节是虚函数表的指针,后8个字节是变量ibase1的值。
第二部分是基类Base2的,共2*8=16个字节,前8个字节是虚函数表的指针,后8个字节是变量ibase2的值。
第三部分是基类Base3的,共2*8=16个字节,前8个字节是虚函数表的指针,后8个字节是变量ibase3的值。
第四部分是派生类Derive的,共1*8=8个字节,这8个字节保存着变量iderive的值。
对象d合计3*16+8=56个字节。
同预计一样,派生类Derive中的虚函数依然保存在和基类Base1在一起,紧跟在其后面。而派生类Derive中的虚函数f 分别覆盖掉3个基类中的虚函数f ,并且存在于他们各自的虚函数表中。
验证了如下这副图(NULL就是那个标志1):
编译器:g++ (Ubuntu 4.9.2-10ubuntu13) 4.9.2
环境:ubuntu 15.04 64位系统(地址占8字节)
例子1:
单一的一般继承,所有函数均是virtual,每个类中均有1个long long变量:
class Parent { public: LL iparent; Parent ():iparent (10) {} virtual void f() { cout << " 1 " << endl; } virtual void g() { cout << " 2 " << endl; } virtual void h() { cout << " 3 " << endl; } }; class Child : public Parent { public: LL ichild; Child():ichild(100) {} virtual void f() { cout << " 11 " << endl; } virtual void g_child() { cout << " 4 " << endl; } virtual void h_child() { cout << " 5 " << endl; } }; class GrandChild : public Child{ public: LL igrandchild; GrandChild():igrandchild(1000) {} virtual void f() { cout << " 111 " << endl; } virtual void g_child() { cout << " 44 " << endl; } virtual void h_grandchild() { cout << " 6 " << endl; } }; int main(void) { typedef void(*Fun)(void); GrandChild gc; LL** pVtab = (LL**)&gc; Fun pFun=NULL; cout << "[0] GrandChild::_vptr->" << endl; for (int i=0; i<6; i++){ pFun = (Fun)pVtab[0][i]; cout << " ["<<i<<"] "; pFun(); } LL *p=(LL*)&gc; cout << "[1] Parent.iparent = " <<*(p+1)<< endl; cout << "[2] Child.ichild = " << *(p+2)<< endl; cout << "[3] GrandChild.igrandchild = " << *(p+3)<< endl; cout<<"对象gc的大小:"<<sizeof(gc)<<endl; return 0; }
输出:
、
解释:继承关系为 Parent<--Child<--GrandChild,其中Child是中间类,覆盖了Parent中一个函数f。GrandChild是最底派生类,覆盖了Parent和Child的函数f,再覆盖Child中的函数g_child。
总结:GrandChild的对象gc一共占4*8=32个字节。其中首项是虚函数表指针,剩下的3项是3个类中的成员变量,按继承的声明顺序排列。虚函数表中有6个函数,其中来自GrandChild的有3个,Child的1个,Parent的2个。GrandChild的3个必定不会少,而且前两个函数f和g_child就覆盖掉所有基类中同名的函数。Parent中剩下2个没有被覆盖的,而f不幸被覆盖。Child中只剩下1个了,前两个都被覆盖了。
其实可以先看两层的Parent<--Child,而GrandChild先不看。那么派生类覆盖掉基类1个函数,剩下的就是2+3=5个函数。来了新的基类GrandChild后,也新来3个函数,但是其中2个已经存在,先覆盖,剩下一个直接加进去,即5+1=6个函数。
验证了如下这副图:
例子2:
多重继承,基类与派生类中有同名虚函数,每个类中有自己的1个变量。
class Base1 { public: LL ibase1; Base1():ibase1(10) {} virtual void f() { cout << "Base1::f()" << endl; } virtual void g() { cout << "Base1::g()" << endl; } virtual void h() { cout << "Base1::h()" << endl; } }; class Base2 { public: LL ibase2; Base2():ibase2(20) {} virtual void f() { cout << "Base2::f()" << endl; } virtual void g() { cout << "Base2::g()" << endl; } virtual void h() { cout << "Base2::h()" << endl; } }; class Base3 { public: LL ibase3; Base3():ibase3(30) {} virtual void f() { cout << "Base3::f()" << endl; } virtual void g() { cout << "Base3::g()" << endl; } virtual void h() { cout << "Base3::h()" << endl; } }; class Derive : public Base1, public Base2, public Base3 { public: LL iderive; Derive():iderive(100) {} virtual void f() { cout << "Derive::f()" << endl; } virtual void g1() { cout << "Derive::g1()" << endl; } }; int main(void) { typedef void(*Fun)(void); Derive d; Fun pFun=NULL; LL** pVtab = (LL**)&d; cout << "[0] Base1::_vptr->" << endl; for(int i=0; i<4; i++) { pFun = (Fun)pVtab[0][i]; cout << " ["<<i<<"] "; pFun(); } pFun = (Fun)pVtab[0][4]; cout << " [4] "<<pFun<<endl; cout << "[1] Base1.ibase1 = " << (LL)pVtab[1] << endl; cout << "[4] Base3::_vptr->" << endl; for(int i=0; i<3; i++) { pFun = (Fun)pVtab[4][i]; cout << " ["<<i<<"] "; pFun(); } pFun = (Fun)pVtab[4][4]; cout << " [4] "<<pFun<<endl; cout << "[5] Base3.ibase3 = " << (LL)pVtab[5] << endl; return 0; }
输出:
解释:代码中只测试了派生类对象d的4*8=32个字节的内容(不连续的)。其他的推测一下就行了。对于[0][4]和[2][4]其实是一个标志,1表示后面没有指针了,是编译器做的手脚(上一篇中讲了无法验证,在这验证了)。
总结:对象d中分4个部分。
第一部分是基类Base1的,共2*8=16个字节,前8个字节是虚函数表的指针,后8个字节是变量ibase1的值。
第二部分是基类Base2的,共2*8=16个字节,前8个字节是虚函数表的指针,后8个字节是变量ibase2的值。
第三部分是基类Base3的,共2*8=16个字节,前8个字节是虚函数表的指针,后8个字节是变量ibase3的值。
第四部分是派生类Derive的,共1*8=8个字节,这8个字节保存着变量iderive的值。
对象d合计3*16+8=56个字节。
同预计一样,派生类Derive中的虚函数依然保存在和基类Base1在一起,紧跟在其后面。而派生类Derive中的虚函数f 分别覆盖掉3个基类中的虚函数f ,并且存在于他们各自的虚函数表中。
验证了如下这副图(NULL就是那个标志1):
相关文章推荐
- 约瑟夫环-顺序表-C++
- 腾讯c++ 阿里java
- 小顶堆第二弹-----堆降序排序(C语言非递归)
- C++编程规范---读书有感(2)
- C语言——符号的声明与定义
- C++类中的枚举类型
- C语言---迷宫游戏 代码
- C++中的单例模式其实也不简单
- C++文件 读写操作大全
- 剑指offer第三十一题【整数中1出现的次数(从1到n整数中1出现的次数)】c++实现
- C语言之动态内存分配
- C++的虚函数表
- C++输入输出操作符重载
- C++输入输出操作符重载
- 【c++】公司职员系统
- 【C++】多态的实现原理
- 单片机C语言代码-代码格式
- C语言之指针高级
- 查询词提示系统的简单实现
- c语言:3种方法;求出0~999之间的所有“水仙花数”并输出。