C++类对象内存模型是一个比较抓狂的问题,主要是C++特性太多了,所以必须建立一个清晰的分析层次。一般而言,讲到C++对象,都比较容易反应到以下这个图表:
这篇文章,就以这个表格作为分析和行文的策略的纵向指导;横向上,兼以考虑无继承、单继承、多重继承及虚拟继承四方面情况,这样一来,思维层次应该算是比较清晰了。
1、C++类数据成员的内存模型
1.1 无继承情况
实验最能说明问题了,首先考虑下面一个简单的程序1:
#include<iostream>
class memtest
{
public:
memtest(int _a, double _b) : a(_a), b(_b) {}
inline void print_addr(){
std::cout<<"Address of a and b is:/n/t/t"<<&a<<"/n/t/t" <<&b<<"/n";
}
inline void print_sta_mem(){
std::cout<<"Address of static member c is:/n/t/t"<<&c<<"/n";
}
private:
int a;
double b;
static int c;
};
int memtest::c = 8;
int main()
{
memtest m(1,1.0);
std::cout<<"Address of m is : /n/t/t"<< &m<<"/n";
m.print_addr();
m.print_sta_mem();
return 0;
}
在GCC4.4.5下编译,运行,结果如下:
可以发现以下几点:
1. 非静态数据成员a的存储地址就是从类的实例在内存中的地址中(本例中均为0xbfadfc64)开始的,之后的double b也紧随其后,在内存中连续存储;
2. 对于静态数据成员c,则出现在了一个很“莫名其妙”的地址0x804a028上,与类的实例的地址看上去那是八竿子打不着;
其实不做这个测试,关于C++数据成员存储的问题也都是C++ Programmer的常识,对于非静态数据成员,一般编译器都是按其在类中声明的顺序存储,而且数据成员的起始地址就是类得实例在内存中的起始地址,这个在上面的测试中已经很明显了。对非静态数据成员的读写,我们可以这样想,其实C++程序完全可以转换成对应的C程序来编写,有一些C++编译器编译C++程序时就是这样做的。对非静态数据成员的读写也可以借助这个等价的C程序来理解。考虑下面代码段2: