具有虚函数的类的大小 & VS2010命令行查看虚函数表和类内存布局
虚函数的原理可查看
http://www.cnblogs.com/malecrab/p/5572730.html
http://www.cnblogs.com/malecrab/p/5573368.html
一 VS2010命令行查看虚函数表和类内存布局
以下内容引自<http://blog.****.net/daydreamingboy/article/details/8982563>
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,对于学习上述的示例代码绰绰有余咯~~
二 具有虚函数的类的大小
- struct Base {
- virtual void f();
- virtual void g();
- };
- struct Derived : public Base {
- virtual void f();
- virtual void g();
- };
- Base *pB = new Derived();
- pB->f();
先让我们看Base和Derived对象是怎么存储的,以及两个类的虚表结构
Base: VTable_B:
------------ -------------
| vptr | | f() 入口 |
+---------+ +----------+
| Base的 | | g() 入口 |
| 数据 | -------------
------------
Derived: VTable_D:
------------ --------------
| vptr | | f() 入口 |
+---------+ +----------+
| Base的 | | g() 入口 |
| 数据 | -------------
+---------+
| Derived的|
| 数据 |
------------
等到虚函数被调用的时候,也就是 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的| -------------
| 数据 |
三 案例分析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
- 请按任意键继续. . .
案例分析:
四 案例分析二:
空类所占内存大小:
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;
- }
六 案例分析四:
- 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;
- }
同时要注意,空基类优化只能存在于基类子对象,当空类对象作为完整对象时,是不能优化的,因为C++规定,每个完整对象都必须具有唯一的地址。空类完整对象的大小并不是只能为1,而是至少为1,有些编译器会加入内存对齐的因素,所以有些编译器的空类完整对象的大小会是4或者8等等。
- 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
- 请按任意键继续. . .
转载自:http://blog.****.net/qianqin_2014/article/details/51464007