转载

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 }

结果:

C++对象模型之我见(一)

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

现在我们再看一个例子,我们让类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 }

结果:

C++对象模型之我见(一)

在这里我们增加了一个静态数据成员d,一个静态成员函数fun1,非静态的成员函数fun2

一个虚函数fun3.不难发现就类的代码规模来说,的确增加了不少,然而结果仅仅比之前增

加了四个字节的开销。 《深度探索C++对象模型》中有这样一句话说C++封装所带来的开销

主要来源于虚函数。

那么我们可以先看看结构体A的内存分布:

可以产生结构体A的一个对象:

1     A *a; 2     a=new A;

对象a的内存结构如下:

C++对象模型之我见(一)

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

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

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

1     B *b; 2     b=new B;

对象B的内存结构如下:

C++对象模型之我见(一)

可以看到尽管类B封装的很多的内容,但是较之A而言仅仅多了一个

__vfptr,我们不禁回忆起前面的一句话,C++的封装的开销主要

来自于虚函数。其实如果对C++有一定了解的同学都不难猜出,这个

ptr其实就是一个虚表指针。正因为这个虚表指针给封装带来了额外的

4个字节的开销。

那么虚表指针是什么呢,虚表指针其实就是指向虚函数表的一个指针。

当一个类中有虚函数的时候,类会自动生成一个指针,该指针保存的

是该类中第一个虚函数的地址。所以就算有多个虚函数仍然只需要保存

一个虚表指针,然后通过这个指针逐个遍历就可以取得各个虚函数的地址。

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

C++对象模型之我见(一)

这下一目了然了吧。

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

1.非静态的数据成员

2.虚函数表指针

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

还有几点需要注意:

1.类的静态数据成员存储在全局变量区,不带来C++内存开销,该静态数据成员属于整个类的

不属于具体某个对象,其初始化要在类外进行。

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

员函数中不能调用非静态的成员变量。

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

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

C++对象模型之我见(一)

正文到此结束
Loading...