具有虚函数的类的大小 & VS2010命令行查看虚函数表和类内存布局

虚函数的原理可查看

http://www.cnblogs.com/malecrab/p/5572730.html

http://www.cnblogs.com/malecrab/p/5573368.html


一 VS2010命令行查看虚函数表和类内存布局


以下内容引自<http://blog.csdn.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)为例,如下

  1. class Base1 {  
  2. public:  
  3.     Base1();  
  4.     virtual ~Base1();  
  5.     virtual void speackClearly();  
  6.     virtual Base1* clone() const;  
  7. protected:  
  8.     float data_Base1;  
  9. };  
  10.   
  11. class Base2 {  
  12. public:  
  13.     Base2();  
  14.     virtual ~Base2();  
  15.     virtual void mumble();  
  16.     virtual Base2* clone() const;  
  17. protected:  
  18.     float data_Base2;  
  19. };  
  20.   
  21. class Derived : public Base1, public Base2 {  
  22. public:  
  23.     Derived();  
  24.     virtual ~Derived();  
  25.     virtual Derived* clone() const;  
  26. protected:  
  27.     float data_Derived;  
  28. };  
  29.   
  30. int main(void)  
  31. {  
  32.     return 0;  
  33. }  

3、使用cl命令的/d1 reportAllClassLayout或reportSingleClassLayoutXXX选项。这里的reportAllClassLayout选项会打印大量相关类的信息,一般用处不大。而reportSingleClassLayoutXXX选项的XXX代表要编译的代码中类的名字(这里XXX类),打印XXX类的内存布局和虚函数表(如果代码中没有对应的类,则选项无效)。

举例如下

  1. cl /d1 reportSingleClassLayoutBase1 160.cpp  

运行结果下


可以看出Base1的大小为8个字节,共有3个虚函数,分别是~Base1、speackClearly和clone,对于学习上述的示例代码绰绰有余咯~~


二 具有虚函数的类的大小



一般的书上都说,虚函数是在运行时根据对象的实际类型“动态决定”函数入口。但什么是“动态决定”呢?实际上C++编译器在实现这个功能的时候,并非真的 等到虚函数被调用时才去判断这个对象是什么类型的。下面我用一个简单的图表来说明C++编译器到底干了些什么。假设有两个类
  1. struct Base {  
  2.   virtual void f();  
  3.   virtual void g();  
  4. };  
  5. struct Derived : public Base {  
  6.   virtual void f();  
  7.   virtual void g();  
  8. };  

Base 和 Derived 各有一个虚表,分别是 VTable_B 和 VTable_D ,那么编译器是怎么通过只用一个虚表指针来实现下面的“动态”调用呢?
  1. Base *pB = new Derived();  
  2. 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的|        -------------
|    数据     |

本文版权归作者 kanego 和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.

三 案例分析1:


如下程序:
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4.   
  5.   
  6. class A  
  7. {  
  8.     int a;  
  9. public:  
  10.     virtual void af(){}  
  11.     virtual ~A(){}//如果注释,那么class D的大小变为12,这点想不通  
  12. };  
  13.   
  14. class B : public A  
  15. {  
  16.     int b;  
  17. public:  
  18.     virtual void bf(){}  
  19.     virtual ~B(){ /*cout << "B::~B()" << endl;*/ }  
  20. };  
  21.   
  22. class C : public A  
  23. {  
  24.     int c;  
  25. public:  
  26.     virtual void cf(){}  
  27.     virtual ~C(){ /*cout << "B::~B()" << endl;*/ }  
  28. };  
  29.   
  30. class D :public B, public C  
  31. {  
  32.     int d;  
  33. public:  
  34.     virtual void df(){}  
  35.     virtual ~D() { /*cout << "D::~D()" << endl;*/ }  
  36. };  
  37.   
  38. int main(void)  
  39. {  
  40.     cout << "A=" << sizeof(A) << endl;    //result=1  
  41.     cout << "B=" << sizeof(B) << endl;    //result=8      
  42.     cout << "C=" << sizeof(C) << endl;    //result=8  
  43.     cout << "D=" << sizeof(D) << endl;    //result=12  
  44.     //cout << "E=" << sizeof(E) << endl;    //result=20  
  45.   
  46.     system("pause");  
  47.     return 0;  
  48. }  

输出结果:
  1. A=8  
  2. B=12  
  3. C=12  
  4. D=28  
  5. 请按任意键继续. . .  

案例分析:
类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。


程序:
  1. class A  
  2. {  
  3. };  
  4. class A2  
  5. {  
  6. };  
  7.   
  8. class B :public A  
  9. {  
  10. };  
  11.   
  12. class D :public A, public A2  
  13. {  
  14. };  
  15.   
  16. class C :public virtual B  
  17. {  
  18. };  
  19.   
  20. int main()  
  21. {  
  22.     cout << sizeof(A) << endl;  
  23.     cout << sizeof(B) << endl;  
  24.     cout << sizeof(C) << endl;  
  25.     cout << sizeof(D) << endl;  
  26.   
  27.     system("pause");  
  28.     return 0;  
  29. }  

运行结果:
  1. 1  
  2. 1  
  3. 4  
  4. 1  
  5. 请按任意键继续. . .  

注:B继承A存在空白基类优化现象,在空基类被继承后(单继承),由于空基类没有任何数据成员,所以让其在子类的对象布局中优化掉空基类所占用的一个字节。


说明:空类、单一继承的空类、多重继承的空类空间为1, 但是虚继承涉及到虚表(虚指针),所以sizeof(C)的大小为4

五 案例分析三:

  1. class A  
  2. {  
  3. };  
  4.   
  5. class B :public A  
  6. {  
  7. public:  
  8.     virtual void f1(){}  
  9. };  
  10.   
  11. class C :public A  
  12. {  
  13. public:  
  14.     virtual void f1(){}  
  15. };  
  16.   
  17. class D :public virtual B,C  
  18. {  
  19. };  
  20.   
  21. int main()  
  22. {  
  23.     cout << sizeof(A) << endl;  
  24.     cout << sizeof(B) << endl;  
  25.     cout << sizeof(C) << endl;  
  26.     cout << sizeof(D) << endl;  
  27.   
  28.     system("pause");  
  29.     return 0;  
  30. }  

为什么sizeof(D) =12呢?

六 案例分析四:


  1. class A  
  2. {  
  3. };  
  4. class B  
  5. {  
  6. };  
  7. class D  
  8. {  
  9. };  
  10. class E  
  11. {  
  12. };  
  13. class F  
  14. {  
  15. };  
  16.   
  17. class C:public A,public B,public D,public E,public F  
  18. {  
  19. };  
  20.   
  21. class M :public A, public B  
  22. {  
  23.     //大小1  
  24. };  
  25.   
  26. int main()  
  27. {  
  28.   
  29.     cout << sizeof(C) << endl;  
  30.     cout << sizeof(M) << endl;  
  31.   
  32.     system("pause");  
  33.     return 0;  
  34. }  

输出结果4,1为什么呢?
多继承的情况下,为了区分各个不同的基类子对象,这些基类子对象必须具有不同的地址,所以这时候不能使用空基类优化,但单继承就可以,因为对于单继承,基类子对象与最终派生类对象地址相同的情形是允许的。

同时要注意,空基类优化只能存在于基类子对象,当空类对象作为完整对象时,是不能优化的,因为C++规定,每个完整对象都必须具有唯一的地址。空类完整对象的大小并不是只能为1,而是至少为1,有些编译器会加入内存对齐的因素,所以有些编译器的空类完整对象的大小会是4或者8等等。

注:若A含有静态数据成员,但是在c++里,静态成员被单独存储在全局区(静态区),所以它同样不影响A的大小。

总结:

  1. class A//sizeof(A)=1  
  2. {};  
  3.   
  4. class S:public A//单继承,空基类优化,sizeof(S)=4  
  5. {  
  6.     int a;  
  7. };  
  8.   
  9. class B//B是空类  
  10. {  
  11.     //没有任何数据成员的类成为空类,这里的数据成员不仅仅包括类的成员变量  
  12.     //同时还包括编译器为了某种目的引入的数据成员  
  13.     //比如:为了支持多态而引入的虚指针vptr,为了支持虚继承而引入的必要的虚基类指针,而且还包括从  
  14.     //基类直接或间接继承而来的上述的数据成员。  
  15.     void fun(){}  
  16. };  
  17.   
  18. class C  
  19. {  
  20. };  
  21.   
  22. class D:public A, B//双继承,空基类优化,但是只能优化一个,sizeof(D)=8  
  23. {  
  24.     int a;  
  25. };  
  26.   
  27. class E:public A, B, C//三重继承,空基类优化一个,sizeof(E)=2  
  28. {  
  29. };  
  30.   
  31. class F:public E//单继承且E为空,空基类优化, sizeof(F)=1;  
  32. {  
  33.     void fun(){}  
  34. };  
  35.   
  36. class G:public F//单继承,空基类优化,sizeof(G)=1  
  37. {  
  38. };  
  39.   
  40. class M{ int a; };  
  41. class N :public M{};  
  42. class O :public N{};  
  43. class P :public N, A{};  
  44. class Q :public N, A, B{};  
  45.   
  46. int main()  
  47. {  
  48.   
  49.     cout << "A" << sizeof(A) << endl;  
  50.     cout << "S" << sizeof(S) << endl;  
  51.     cout << "B" << sizeof(B) << endl;  
  52.     cout << "C" << sizeof(C) << endl;  
  53.     cout << "D" << sizeof(D) << endl;  
  54.     cout << "E" << sizeof(E) << endl;  
  55.     cout << "F" << sizeof(C) << endl;  
  56.     cout << "M" << sizeof(M) << endl;  
  57.     cout << "N" << sizeof(N) << endl;  
  58.     cout << "O" << sizeof(O) << endl;  
  59.     cout << "P" << sizeof(P) << endl;  
  60.     cout << "Q" << sizeof(Q) << endl;  
  61.   
  62.   
  63.     system("pause");  
  64.     return 0;  
  65. }  
  66.   
  67. //书上说:通常C++默默安插一个char到空对象内  

运行结果:
  1. A1  
  2. S4  
  3. B1  
  4. C1  
  5. D8  
  6. E2  
  7. F1  
  8. M4  
  9. N4  
  10. O4  
  11. P4  
  12. Q8  
  13. 请按任意键继续. . . 



转载自:http://blog.csdn.net/qianqin_2014/article/details/51464007

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值