c++对象内存模型【内存布局】
2015-08-21 16:36
246 查看
#类中的元素
0. 成员变量 1. 成员函数 2. 静态成员变量 3. 静态成员函数
4. 虚函数 5. 纯虚函数
#影响对象大小的因素
0. 成员变量 1. 虚函数表指针(_vftptr) 2. 虚基类表指针(_vbtptr)
3. 内存对齐
_vftptr、_vbtptr的初始化由对象的构造函数, 赋值运算符自动完成;对象生命周期结束后,由对象的析构函数来销毁。
对象所关联的类型(type_info),通常放在virtual table的第一个slot中。
虚继承:在继承定义中包含了virtual关键字的继承关系;
虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:
class CDerive : public virtual CBase {}; 其中CBase称之为CDerive的虚基类,而不是说CBase就是个虚基类,因为CBase还可以为不是虚继承体系中的基类。
虚函数被派生后,仍然为虚函数,即使在派生类中省去virtual关键字。
注:【下文中_vbptr即_vbtptr】
#对象内存布局分类讨论
vc6变量查看器中(Locals,Watch1等),也可以看到部分对象布局的情况(不完整,且虚继承是错误的)。
vs2005及以后版本的编译器提供了/d1reportSingleClassLayout[类名]编译选项来查看对象完整的内存布局:
0. 单一类
(1). 空类
![](http://images.cnitblog.com/blog/78946/201301/20161511-1866d713a7f0496780bc009bade8a9dd.png)
sizeof(CNull)=1(用于标识该对象)
(2). 只有成员变量的类
![](http://images.cnitblog.com/blog/78946/201301/20161852-eded6e6e00104d70a5006c8bdfabc8d5.png)
int nVarSize = sizeof(CVariable) = 12
![](http://images.cnitblog.com/blog/78946/201301/20123116-50ef736604fe4a11b11c855988aa6aed.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/20185827-e8d325e53dff40868260b0caa5d8f0e5.png)
(3). 只有虚函数的类
![](http://images.cnitblog.com/blog/78946/201301/20162224-8dd974c9f2eb402fb4ee934f8f22e7fa.png)
int nVFuntionSize = sizeof(CVFuction) = 4(虚表指针)
![](http://images.cnitblog.com/blog/78946/201301/20123423-5770117b4e5247a9ae7e0e32268fd65e.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/20190542-19448cec698840069f24334afa858213.png)
(4). 有成员变量、虚函数的类
![](http://images.cnitblog.com/blog/78946/201301/20162737-04470e088b97430eb1a86207c0c344cd.png)
int nParentSize = sizeof(CParent) = 8
![](http://images.cnitblog.com/blog/78946/201301/20191121-010e66669acf456186803ff839dcd048.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/20191007-da15a78a8e4d48469ae59f3528cfba7d.png)
1. 单一继承(含成员变量、虚函数、虚函数覆盖)
![](http://images.cnitblog.com/blog/78946/201301/20192826-ab5150ea811844f38f0392a1b85075a4.png)
int nChildSize = sizeof(CChildren)
= 12
vc中显示的结果(注:还有1个虚函数CChildren::g1没有被显示出来):
![](http://images.cnitblog.com/blog/78946/201301/20192154-f2055b9755cb49788b0fa66f5c86a3bf.png)
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27120846-3f36962313604194be3afd4ce0435b48.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/20195722-7b59876a02da4840be19807bb74ba374.png)
2. 多继承 (含成员变量、虚函数、虚函数覆盖)
![](http://images.cnitblog.com/blog/78946/201301/20194809-ff8b1c34ca0f4516b2889f6077bb5065.png)
int nChildSize = sizeof(CChildren) = 20
vc中显示的结果(注:还有2个虚函数CChildren::f2,CChildren::h2没有被显示出来,this指针的adjustor[调整值]也没打印出):
![](http://images.cnitblog.com/blog/78946/201301/20193849-5dd1ed6546224f15b8b4f46597c70029.png)
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27121206-27a4eaedddc04228b8fcd2d673ccc5c5.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/20195528-bc778d9884b9443586e326619e2afc18.png)
3. 深度为2的继承(含成员变量、虚函数、虚函数覆盖)
![](http://images.cnitblog.com/blog/78946/201301/20201343-cd8b42e4891a45049f0bc51a7b9bcba1.png)
int nGrandSize = sizeof(CGrandChildren) = 24
vc中显示的结果(注:还有3个虚函数CGrandChildren::f2,CChildren::h2,CGrandChildren::f3没有显示出来,this指针的adjustor[调整值]也没打印出):
![](http://images.cnitblog.com/blog/78946/201301/20201638-d5ba2490b38d47d68fd66429bee4666d.png)
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27121733-1bb2ca5c65cc4324bd042b9c35bdfc43.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/20202629-6c67cc62beec44299fcde9c24fc1bbd1.png)
4 重复继承(含成员变量、虚函数、虚函数覆盖)
![](http://images.cnitblog.com/blog/78946/201301/20233722-923833f1aabd410a9d7eaa10539bc05b.png)
int nGrandSize = sizeof(CGrandChildren) = 28
vc中显示的结果(注:还有大量的虚函数没有显示出来,this指针的adjustor[调整值]也没打印出):
thunk函数:一种形实转换辅助函数;主要做this指针调整,函数调用重定向。
![](http://images.cnitblog.com/blog/78946/201301/20233815-4c1f20af25a146c4a2eed8a5a7028aee.png)
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27133845-16bff4cf175b43c6b5e5efd1f7a0f48b.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/27134649-f1af0fa22b534a5da0076f70746b3a57.png)
由于m_nAge在内容中存在两个拷贝,因此我们不能直接通过pGrandChildrenA->m_nAge来访问该变量,
这样会存在二义性,编译器无法知道应该访问CChildren1中的m_nAge,还是CChildren2中的m_nAge。
为了标识唯一的m_nAge,就需要带上其所在范围的类名了。如下:
5. 单一虚继承(含成员变量、虚函数、虚函数覆盖)
![](http://images.cnitblog.com/blog/78946/201301/27142533-002c68535a4a463da49c7f149ddbdcb7.png)
int nChildSize = sizeof(CChildren) = 20
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27143035-dfed0c325f6049e7a083339187d34eb0.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/27154246-54ba3f1cc4cb4af995cad6a1d1c409f4.png)
6. 多虚继承(含成员变量、虚函数、虚函数覆盖)
(1) virtual CParent1, CParent2
![](http://images.cnitblog.com/blog/78946/201301/27160655-89c81bec9ff44976bd18ca3bb155c3c7.png)
int nChildSize = sizeof(CChildren) = 24
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27160246-6dbae00a46ac4e1bacb618790c8d97f4.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/27161739-e36a050cd15f45bfab9102e3798f869e.png)
(2) CParent1, virtual CParent2
![](http://images.cnitblog.com/blog/78946/201301/27160708-662f2ad380224a9ca23aa22e6ce14339.png)
int nChildSize = sizeof(CChildren) = 24
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27160311-8bdcc85344a8489c8ef9cf31b68811a7.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/27162255-80ac349691ed4d79bd85f080ea0b2374.png)
(3) virtual CParent1, virtual CParent2
![](http://images.cnitblog.com/blog/78946/201301/27160722-f5a4b0bae44c4349a059c3896374fadf.png)
int nChildSize = sizeof(CChildren) = 28
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27160353-5fe2320ad5c548e5a612e9741e4ea223.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/27163043-eda211cc4bb64d1da3774b1ba397bebc.png)
7. 钻石型的虚拟多重继承(含成员变量、虚函数、虚函数覆盖)
![](http://images.cnitblog.com/blog/78946/201301/27163844-d4287a5050cf4b2b9c0c32a21338ff2f.png)
int nGrandChildSize = sizeof(CGrandChildren) = 36
d1reportSingleClass查看:
thunk函数:一种形实转换辅助函数;主要做this指针调整,函数调用重定向。
![](http://images.cnitblog.com/blog/78946/201301/27164403-1bcf8354b3724e6aa19e67e781337e5f.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/27165612-e0b8e91bc6b645e6a765aa6df4c856b4.png)
#外部参考
C++类对应的内存结构
陈皓- C++ 虚函数表解析
陈皓 - C++ 对象的内存布局(上)
陈皓 - C++ 对象的内存布局(下)
0. 成员变量 1. 成员函数 2. 静态成员变量 3. 静态成员函数
4. 虚函数 5. 纯虚函数
#影响对象大小的因素
0. 成员变量 1. 虚函数表指针(_vftptr) 2. 虚基类表指针(_vbtptr)
3. 内存对齐
_vftptr、_vbtptr的初始化由对象的构造函数, 赋值运算符自动完成;对象生命周期结束后,由对象的析构函数来销毁。
对象所关联的类型(type_info),通常放在virtual table的第一个slot中。
虚继承:在继承定义中包含了virtual关键字的继承关系;
虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:
class CDerive : public virtual CBase {}; 其中CBase称之为CDerive的虚基类,而不是说CBase就是个虚基类,因为CBase还可以为不是虚继承体系中的基类。
虚函数被派生后,仍然为虚函数,即使在派生类中省去virtual关键字。
注:【下文中_vbptr即_vbtptr】
#对象内存布局分类讨论
vc6变量查看器中(Locals,Watch1等),也可以看到部分对象布局的情况(不完整,且虚继承是错误的)。
vs2005及以后版本的编译器提供了/d1reportSingleClassLayout[类名]编译选项来查看对象完整的内存布局:
cl classLayout.cpp /d1reportSingleClassLayoutCChildren
0. 单一类
(1). 空类
![](http://images.cnitblog.com/blog/78946/201301/20161511-1866d713a7f0496780bc009bade8a9dd.png)
sizeof(CNull)=1(用于标识该对象)
(2). 只有成员变量的类
![](http://images.cnitblog.com/blog/78946/201301/20161852-eded6e6e00104d70a5006c8bdfabc8d5.png)
int nVarSize = sizeof(CVariable) = 12
![](http://images.cnitblog.com/blog/78946/201301/20123116-50ef736604fe4a11b11c855988aa6aed.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/20185827-e8d325e53dff40868260b0caa5d8f0e5.png)
(3). 只有虚函数的类
![](http://images.cnitblog.com/blog/78946/201301/20162224-8dd974c9f2eb402fb4ee934f8f22e7fa.png)
int nVFuntionSize = sizeof(CVFuction) = 4(虚表指针)
![](http://images.cnitblog.com/blog/78946/201301/20123423-5770117b4e5247a9ae7e0e32268fd65e.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/20190542-19448cec698840069f24334afa858213.png)
(4). 有成员变量、虚函数的类
![](http://images.cnitblog.com/blog/78946/201301/20162737-04470e088b97430eb1a86207c0c344cd.png)
int nParentSize = sizeof(CParent) = 8
![](http://images.cnitblog.com/blog/78946/201301/20191121-010e66669acf456186803ff839dcd048.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/20191007-da15a78a8e4d48469ae59f3528cfba7d.png)
1. 单一继承(含成员变量、虚函数、虚函数覆盖)
![](http://images.cnitblog.com/blog/78946/201301/20192826-ab5150ea811844f38f0392a1b85075a4.png)
int nChildSize = sizeof(CChildren)
= 12
vc中显示的结果(注:还有1个虚函数CChildren::g1没有被显示出来):
![](http://images.cnitblog.com/blog/78946/201301/20192154-f2055b9755cb49788b0fa66f5c86a3bf.png)
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27120846-3f36962313604194be3afd4ce0435b48.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/20195722-7b59876a02da4840be19807bb74ba374.png)
2. 多继承 (含成员变量、虚函数、虚函数覆盖)
![](http://images.cnitblog.com/blog/78946/201301/20194809-ff8b1c34ca0f4516b2889f6077bb5065.png)
int nChildSize = sizeof(CChildren) = 20
vc中显示的结果(注:还有2个虚函数CChildren::f2,CChildren::h2没有被显示出来,this指针的adjustor[调整值]也没打印出):
![](http://images.cnitblog.com/blog/78946/201301/20193849-5dd1ed6546224f15b8b4f46597c70029.png)
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27121206-27a4eaedddc04228b8fcd2d673ccc5c5.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/20195528-bc778d9884b9443586e326619e2afc18.png)
3. 深度为2的继承(含成员变量、虚函数、虚函数覆盖)
![](http://images.cnitblog.com/blog/78946/201301/20201343-cd8b42e4891a45049f0bc51a7b9bcba1.png)
int nGrandSize = sizeof(CGrandChildren) = 24
vc中显示的结果(注:还有3个虚函数CGrandChildren::f2,CChildren::h2,CGrandChildren::f3没有显示出来,this指针的adjustor[调整值]也没打印出):
![](http://images.cnitblog.com/blog/78946/201301/20201638-d5ba2490b38d47d68fd66429bee4666d.png)
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27121733-1bb2ca5c65cc4324bd042b9c35bdfc43.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/20202629-6c67cc62beec44299fcde9c24fc1bbd1.png)
4 重复继承(含成员变量、虚函数、虚函数覆盖)
![](http://images.cnitblog.com/blog/78946/201301/20233722-923833f1aabd410a9d7eaa10539bc05b.png)
int nGrandSize = sizeof(CGrandChildren) = 28
vc中显示的结果(注:还有大量的虚函数没有显示出来,this指针的adjustor[调整值]也没打印出):
thunk函数:一种形实转换辅助函数;主要做this指针调整,函数调用重定向。
![](http://images.cnitblog.com/blog/78946/201301/20233815-4c1f20af25a146c4a2eed8a5a7028aee.png)
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27133845-16bff4cf175b43c6b5e5efd1f7a0f48b.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/27134649-f1af0fa22b534a5da0076f70746b3a57.png)
由于m_nAge在内容中存在两个拷贝,因此我们不能直接通过pGrandChildrenA->m_nAge来访问该变量,
这样会存在二义性,编译器无法知道应该访问CChildren1中的m_nAge,还是CChildren2中的m_nAge。
为了标识唯一的m_nAge,就需要带上其所在范围的类名了。如下:
1 pGrandChildrenA->CChildren1::m_nAge = 1; 2 pGrandChildrenA->CChildren2::m_nAge = 2;
5. 单一虚继承(含成员变量、虚函数、虚函数覆盖)
![](http://images.cnitblog.com/blog/78946/201301/27142533-002c68535a4a463da49c7f149ddbdcb7.png)
int nChildSize = sizeof(CChildren) = 20
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27143035-dfed0c325f6049e7a083339187d34eb0.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/27154246-54ba3f1cc4cb4af995cad6a1d1c409f4.png)
6. 多虚继承(含成员变量、虚函数、虚函数覆盖)
(1) virtual CParent1, CParent2
![](http://images.cnitblog.com/blog/78946/201301/27160655-89c81bec9ff44976bd18ca3bb155c3c7.png)
int nChildSize = sizeof(CChildren) = 24
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27160246-6dbae00a46ac4e1bacb618790c8d97f4.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/27161739-e36a050cd15f45bfab9102e3798f869e.png)
(2) CParent1, virtual CParent2
![](http://images.cnitblog.com/blog/78946/201301/27160708-662f2ad380224a9ca23aa22e6ce14339.png)
int nChildSize = sizeof(CChildren) = 24
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27160311-8bdcc85344a8489c8ef9cf31b68811a7.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/27162255-80ac349691ed4d79bd85f080ea0b2374.png)
(3) virtual CParent1, virtual CParent2
![](http://images.cnitblog.com/blog/78946/201301/27160722-f5a4b0bae44c4349a059c3896374fadf.png)
int nChildSize = sizeof(CChildren) = 28
d1reportSingleClass查看:
![](http://images.cnitblog.com/blog/78946/201301/27160353-5fe2320ad5c548e5a612e9741e4ea223.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/27163043-eda211cc4bb64d1da3774b1ba397bebc.png)
7. 钻石型的虚拟多重继承(含成员变量、虚函数、虚函数覆盖)
![](http://images.cnitblog.com/blog/78946/201301/27163844-d4287a5050cf4b2b9c0c32a21338ff2f.png)
int nGrandChildSize = sizeof(CGrandChildren) = 36
d1reportSingleClass查看:
thunk函数:一种形实转换辅助函数;主要做this指针调整,函数调用重定向。
![](http://images.cnitblog.com/blog/78946/201301/27164403-1bcf8354b3724e6aa19e67e781337e5f.png)
内存布局:
![](http://images.cnitblog.com/blog/78946/201301/27165612-e0b8e91bc6b645e6a765aa6df4c856b4.png)
#外部参考
C++类对应的内存结构
陈皓- C++ 虚函数表解析
陈皓 - C++ 对象的内存布局(上)
陈皓 - C++ 对象的内存布局(下)
相关文章推荐
- Effective C++学习笔记五(实现)
- Performance Log
- 【转】笔试常考:C语言字节对齐
- [黑马程序员]C语言结构体
- C++中的set_new_handler和new_handler 【未完待续】
- C++字符串转数字,数字转字符串
- java语言和C/C++语言的关系
- Z字形扫描
- C++11 右值引用与move语义
- C语言的static和extern关键字的使用
- C/C++编程题之计算一个数字的立方根
- C++类模板的三种特化
- 分步编译一个C语言文件
- 黑马程序员-[C语言-指针和文件操作]学习日记(四)
- C++无名命名空间详解
- C++并发编程学习笔记<1> 入门
- 浅谈C语言中的联合体
- C++ Primer 5e chapter 9.1
- c++ 中文分词介绍
- C语言提高之技术模型层次、学习标准、特点、内存四区、函数调用模型