C++对象模型浅析

本文仅代表博主自己对C++内存对象模型的一点理解,如果文中有

理解偏差和不准确的地方,希望各位大大提出,我好及时改正。

本博文只对博主自己负责,不对任何人负责。

 

就如《深度探索C++对象模型》一书中介绍的C++的封装并没有给C++带来过多的开销。然而面向对象的编程方法却给广大的编程者提供了一种

更为开阔的编程思路。

 

好,我们主要看看前面一句。开销是什么,这里的开销主要指C++类所占内存的空间。首先,我们看这样一个例子,我们定义一个结构体和类,结

构体和类中含有相同的数据成员。除此之外再无其他。我们看看这个结构体和类的大小是多少:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 struct A
 5 {
 6     int a;
 7     int b;
 8     int c;
 9 };
10 
11 class B
12 {
13     int a;
14     int b;
15     int c;
16 };
17 
18 
19 
20 int main()
21 {
22     cout<<"sizeof(A)="<<sizeof(A)<<endl;
23     cout<<"sizeof(B)="<<sizeof(B)<<endl;
24     return 0;
25 }

结果:

看来只要数据成员相同,那么就算声明为类,也不会带来额外的内存开销。

 

现在我们再看一个例子,我们让类B更加复杂一下,我们添加一个静态成员变量和,一个静态成员函数,一个非静态成员函数,一个

虚函数:我们再看看这个例子的结果将是什么样的结果:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 struct A
 5 {
 6     int a;
 7     int b;
 8     int c;
 9 };
10 
11 class B
12 {
13 private:
14     int a;
15     int b;
16     int c;
17     static int d;
18 
19 public:
20     static void fun1()
21     {
22         cout<<"This is a static fun1."<<endl;
23     }
24     
25     void fun2()
26     {
27         cout<<"This is ordinary fun2."<<endl;
28     }
29 
30     virtual void fun3()
31     {
32         cout<<"This is a virtual fun3."<<endl;
33     }
34 };
35 
36 
37 
38 int main()
39 {
40     cout<<"sizeof(A)="<<sizeof(A)<<endl;
41     cout<<"sizeof(B)="<<sizeof(B)<<endl;
42     return 0;
43 }

 

结果:

在这里我们增加了一个静态数据成员d,一个静态成员函数fun1,非静态的成员函数fun2一个虚函数fun3.不难发现就类的代码规模

来说,的确增加了不少,然而结果仅仅比之前增加了四个字节的开销。《深度探索C++对象模型》中有这样一句话说C++封装所带来

的开销主要来源于虚函数。

 

那么我们可以先看看结构体A的内存分布:可以产生结构体A的一个对象:

1     A *a;
2     a=new A;

对象a的内存结构如下:

显然a中有三个整型数据元素a,b,c刚好12个字节

 

现在我们看看类B的内存分布

可以产生类B的一个对象:

1     B *b;
2     b=new B;

对象B的内存结构如下:

可以看到尽管类B封装的很多的内容,但是较之A而言仅仅多了一个__vfptr,我们不禁回忆起前面的一句话,C++的封装的开销主要来自于虚

函数。其实如果对C++有一定了解的同学都不难猜出,这个ptr其实就是一个虚表指针。正因为这个虚表指针给封装带来了额外的4个字节的开销。

 

那么虚表指针是什么呢,虚表指针其实就是指向虚函数表的一个指针。当一个类中有虚函数的时候,类会自动生成一个指针,该指针保存的是该类

中第一个虚函数的地址。所以就算有多个虚函数仍然只需要保存一个虚表指针,然后通过这个指针逐个遍历就可以取得各个虚函数的地址。然而关于

虚函数在C++中功能特性我们留待下次一起学习了。

这下一目了然了吧。

 

 

那么我们可以总结一下影响类的内存开销主要有以下几个方面:

1.非静态的数据成员

2.虚函数表指针

3.当然既然类也是要考虑内存对齐的。

 

还有几点需要注意:

1.类的静态数据成员存储在全局变量区,不带来C++内存开销,该静态数据成员属于整个类的不属于具体某个对象,其初始化要在类外进行。

2.类的静态成员函数是属于整个类的,不属于某个对象,不会带来内存开销。注意类的静态成员函数中不能调用非静态的成员变量。

3.类的普通成员函数始终在程序的代码区中保存一份,不带来内存开销。

 

 

最后再通过一张图片只管展示结构体A和类B的内存布局:

 

posted @ 2015-07-28 11:39  vpoet  阅读(731)  评论(2编辑  收藏  举报