具有虚函数的类的大小 & VS2010命令行查看虚函数表和类内存布局
2016-05-20 19:21
429 查看
一 VS2010命令行查看虚函数表和类内存布局
以下内容引自</article/8362620.html>VS2010命令行下查看虚函数表和类内存布局
——《深度探索C++对象模型》读书札记系列
在学习多重继承下的Virtual functions时,需要分析派生类的虚函数表(vtable),但是在网上找了好几种Hack vtable方法,结果都不尽如人意。没想到MS Compiler(以VS2010为例)有打印vtable的编译选项,真是太好了!
1. 打开“Visual Studio Command Prompt (2010)”,如下
该CMD下具有VS2010命令行下的一些编译、链接等工具,例如cl.exe。
2. 编写一个cpp文件
以《深度探索C++对象模型》的160页的代码(160.cpp)为例,如下
class Base1 { public: Base1(); virtual ~Base1(); virtual void speackClearly(); virtual Base1* clone() const; protected: float data_Base1; }; class Base2 { public: Base2(); virtual ~Base2(); virtual void mumble(); virtual Base2* clone() const; protected: float data_Base2; }; class Derived : public Base1, public Base2 { public: Derived(); virtual ~Derived(); virtual Derived* clone() const; protected: float data_Derived; }; int main(void) { return 0; }
3、使用cl命令的/d1 reportAllClassLayout或reportSingleClassLayoutXXX选项。这里的reportAllClassLayout选项会打印大量相关类的信息,一般用处不大。而reportSingleClassLayoutXXX选项的XXX代表要编译的代码中类的名字(这里XXX类),打印XXX类的内存布局和虚函数表(如果代码中没有对应的类,则选项无效)。
举例如下
cl /d1 reportSingleClassLayoutBase1 160.cpp
运行结果下
可以看出Base1的大小为8个字节,共有3个虚函数,分别是~Base1、speackClearly和clone,对于学习上述的示例代码绰绰有余咯~~
二 具有虚函数的类的大小
以下内容引自<http://www.cnblogs.com/kanego/articles/2390238.html>一般的书上都说,虚函数是在运行时根据对象的实际类型“动态决定”函数入口。但什么是“动态决定”呢?实际上C++编译器在实现这个功能的时候,并非真的 等到虚函数被调用时才去判断这个对象是什么类型的。下面我用一个简单的图表来说明C++编译器到底干了些什么。假设有两个类
struct Base { virtual void f(); virtual void g(); }; struct Derived : public Base { virtual void f(); virtual void g(); };
Base 和 Derived 各有一个虚表,分别是 VTable_B 和 VTable_D ,那么编译器是怎么通过只用一个虚表指针来实现下面的“动态”调用呢?
Base *pB = new Derived(); pB->f();
先让我们看Base和Derived对象是怎么存储的,以及两个类的虚表结构
Base: VTable_B:
------------ -------------
| vptr | | f() 入口 |
+---------+ +----------+
| Base的 | | g() 入口 |
| 数据 | -------------
------------
Derived: VTable_D:
------------ --------------
| vptr | | f() 入口 |
+---------+ +----------+
| Base的 | | g() 入口 |
| 数据 | -------------
+---------+
| Derived的|
| 数据 |
------------
pB
指针既可以指向 Base 对象,也可以指向 Derived 对象,所以 pB 本身是不确定的。但是,任何对象本身却从被 new 出来开始就是确定的,所以 Base 对象在构造时,编译器会往 vptr
中填上 VTable_B 的地址,而 Derived 对象在构造时,编译器会往 vptr 中填上 VTable_D 的地址。
等到虚函数被调用的时候,也就是 pB->f() 这行语句被执行的时候,编译器并不需要知道
pB 到底是指向 Base 还是 Derived ,它只要直接用 vptr 就能找到正确的虚表和虚函数入口了,父类和子类的虚表结构是相似的,同一个虚函数入口在父表和子表的偏移量都是一样的。
通过上面这些介绍,我想你应该能理解,为什么在单一继承的条件下,不管有多少层继承,每个对象只需一个 vptr 就行了。
多重继承的条件下,一个 vptr 行不行呢?
不行。多重继承的时候,虚函数既有来自父类1的,也有来自父类2的,所以这些虚函数入口是不能放在同一个虚表当中的。假设
Derived 除了 Base外,还继承 Base2,并且 Base2 中有两个虚函数 x() 和 y (),那么 Derived 对象的存储结构也许是这样的(只是大概,和具体编译器相关)。
Derived: VTable_D:
------------ --------------
| vptr | | f() 入口 |
+---------+ +----------+
| Base的 | | g() 入口 |
| 数据 | -------------
+---------+
| vptr2 | VTable_D2:
+---------+ -------------
| Base2的 | | x() 入口 |
| 数据 | +-----------+
+---------+ | y() 入口 |
| Derived的| -------------
| 数据 |
以下内容引自<http://www.cnblogs.com/kanego/articles/2390238.html>
本文版权归作者
kanego 和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
三 案例分析1:
如下程序:#include <iostream> using namespace std; class A { int a; public: virtual void af(){} virtual ~A(){}//如果注释,那么class D的大小变为12,这点想不通 }; class B : public A { int b; public: virtual void bf(){} virtual ~B(){ /*cout << "B::~B()" << endl;*/ } }; class C : public A { int c; public: virtual void cf(){} virtual ~C(){ /*cout << "B::~B()" << endl;*/ } }; class D :public B, public C { int d; public: virtual void df(){} virtual ~D() { /*cout << "D::~D()" << endl;*/ } }; int main(void) { cout << "A=" << sizeof(A) << endl; //result=1 cout << "B=" << sizeof(B) << endl; //result=8 cout << "C=" << sizeof(C) << endl; //result=8 cout << "D=" << sizeof(D) << endl; //result=12 //cout << "E=" << sizeof(E) << endl; //result=20 system("pause"); return 0; }
输出结果:
A=8 B=12 C=12 D=28 请按任意键继续. . .
案例分析:
类A有一个虚函数表占4个字节,有自己int类型占4个字节,所以sizeof(A) = 8;
类B继承类A,属于单继承,所以类B只有一个虚函数表指针,占4个字节,继承自A的int变量,还有自身的int变量,所以sizeof(B) = 12;
类C与类B相似;
类D继承自B和C,有两个虚函数表指针,占据8个字节,继承自B的成员变量占8个字节,继承自C的成员变量占8个字节,自身有一个int变量占4字节,所以sizeof(D) = 28。
四 案例分析二:
空类所占内存大小:
class CBase{
};
sizeof(CBase)=1;
为什么空的什么都没有是1呢?
c++要求每个实例在内存中都有独一无二的地址。//注意这句话!!!!!!!!!!
空类也会被实例化,所以编译器会给空类隐含的添加一个字节,这样空类实例化之后就有了独一无二的地址了。所以空类的sizeof为1。
程序:
class A { }; class A2 { }; class B :public A { }; class D :public A, public A2 { }; class C :public virtual B { }; int main() { cout << sizeof(A) << endl; cout << sizeof(B) << endl; cout << sizeof(C) << endl; cout << sizeof(D) << endl; system("pause"); return 0; }
运行结果:
1 1 4 1 请按任意键继续. . .
注:B继承A存在空白基类优化现象,在空基类被继承后(单继承),由于空基类没有任何数据成员,所以让其在子类的对象布局中优化掉空基类所占用的一个字节。
说明:空类、单一继承的空类、多重继承的空类空间为1,但是虚继承涉及到虚表(虚指针),所以sizeof(C)的大小为4。
五 案例分析三:
class A { }; class B :public A { public: virtual void f1(){} }; class C :public A { public: virtual void f1(){} }; class D :public virtual B,C { }; int main() { cout << sizeof(A) << endl; cout << sizeof(B) << endl; cout << sizeof(C) << endl; cout << sizeof(D) << endl; system("pause"); return 0; }
为什么sizeof(D) =12呢?
六 案例分析四:
class A { }; class B { }; class D { }; class E { }; class F { }; class C:public A,public B,public D,public E,public F { }; class M :public A, public B { //大小1 }; int main() { cout << sizeof(C) << endl; cout << sizeof(M) << endl; system("pause"); return 0; }
输出结果4,1为什么呢?
多继承的情况下,为了区分各个不同的基类子对象,这些基类子对象必须具有不同的地址,所以这时候不能使用空基类优化,但单继承就可以,因为对于单继承,基类子对象与最终派生类对象地址相同的情形是允许的。
同时要注意,空基类优化只能存在于基类子对象,当空类对象作为完整对象时,是不能优化的,因为C++规定,每个完整对象都必须具有唯一的地址。空类完整对象的大小并不是只能为1,而是至少为1,有些编译器会加入内存对齐的因素,所以有些编译器的空类完整对象的大小会是4或者8等等。
注:若A含有静态数据成员,但是在c++里,静态成员被单独存储在全局区(静态区),所以它同样不影响A的大小。
总结:
class A//sizeof(A)=1 {}; class S:public A//单继承,空基类优化,sizeof(S)=4 { int a; }; class B//B是空类 { //没有任何数据成员的类成为空类,这里的数据成员不仅仅包括类的成员变量 //同时还包括编译器为了某种目的引入的数据成员 //比如:为了支持多态而引入的虚指针vptr,为了支持虚继承而引入的必要的虚基类指针,而且还包括从 //基类直接或间接继承而来的上述的数据成员。 void fun(){} }; class C { }; class D:public A, B//双继承,空基类优化,但是只能优化一个,sizeof(D)=8 { int a; }; class E:public A, B, C//三重继承,空基类优化一个,sizeof(E)=2 { }; class F:public E//单继承且E为空,空基类优化, sizeof(F)=1; { void fun(){} }; class G:public F//单继承,空基类优化,sizeof(G)=1 { }; class M{ int a; }; class N :public M{}; class O :public N{}; class P :public N, A{}; class Q :public N, A, B{}; int main() { cout << "A" << sizeof(A) << endl; cout << "S" << sizeof(S) << endl; cout << "B" << sizeof(B) << endl; cout << "C" << sizeof(C) << endl; cout << "D" << sizeof(D) << endl; cout << "E" << sizeof(E) << endl; cout << "F" << sizeof(C) << endl; cout << "M" << sizeof(M) << endl; cout << "N" << sizeof(N) << endl; cout << "O" << sizeof(O) << endl; cout << "P" << sizeof(P) << endl; cout << "Q" << sizeof(Q) << endl; system("pause"); return 0; } //书上说:通常C++默默安插一个char到空对象内
运行结果:
A1 S4 B1 C1 D8 E2 F1 M4 N4 O4 P4 Q8 请按任意键继续. . .
相关文章推荐
- scala学习13之与trait同名的object对象
- 人生如此失败,来一瓶82年拉菲醉醉醉醉里苦学工厂模式
- 说说IT技术团队招聘那点事
- HIVE 自定义UDF并上传
- 初识Redis
- 打印出一段英文单词字母大于6个的
- hdoj 1175 连连看(BFS)
- 在JavaScript中模拟类(class)及类的继承关系
- lightoj 1265 Island of Survival
- HDU——1233还是畅通工程(克鲁斯卡尔+优先队列)
- 互联网公司团队建设的几个要点
- LeetCode 342. Power of Four
- 对关系型数据库规范化理论理解的图解
- C++程序错误集锦,如:thiscall,_tmain已经定义,重载,未定义的标识符
- flume介绍及扩展开发心得
- Ioc(Inversion of Control)
- iOS多线程开发--NSThread NSOperation GCD
- 开闭、里氏代换、依赖倒转、单一职责、迪米特五种原则
- [微信开发] 微信网页授权Java实现
- Java学习笔记(六)