`
weiyinchao88
  • 浏览: 1183080 次
文章分类
社区版块
存档分类
最新评论

C++对象的内存布局(上)

 
阅读更多
本文档节选并整理自潘凯的《C++对象布局及多态实现的探索》系列。
1. 空类:
class C000
{
}; // sizeof = 1
它的大小为1字节,这是一个占位符,我们可以看到它的值是0xcc。在debug模式下,这表示是由编译器插入的调试代码所初始化的内存。在release模式下可能是个随机值。
这个字节保证C000类的实例对象拥有不同的内存地址。&a!=&b
2. 仅有普通成员变量的类
class C001
{
int a;
}; // sizeof = 4
(unsigned) char1字节,指针4字节,
(unsigned) short2字节,(unsigned) int 4字节,(unsigned) long4字节,
float4字节,double8字节。
static成员变量存储在全局数据区,并不占用栈中的位置,因此不被sizeof计算在内
class C002
{
char c1[2]; // sizeof = 1
char c2[2]; // sizeof = 1
int i[2]; // sizeof = 4
}; // sizeof = 12
class C003
{
char c1[2]; // sizeof = 1
int i[2]; // sizeof = 4
char c2[2]; // sizeof = 1
}; // sizeof = 16
class C004
{
double d; // sizeof = 8
int i; // sizeof = 4
}; // sizeof = 16
class C005
{
char c[11]; // sizeof = 1
double d; //sizeof = 8
}; // sizeof = 24
要注意成员变量在类中的声明顺序也就是该变量在内存中的存放顺序。
这里采用了内存对齐CPU的优化规则大致原则是这样的:对于n字节的元素(n=2,4,8…),它的首地址能被n整除,才能获得最好的性能。设计编译器的时候可以遵循这个原则:对于每一个变量,可以从当前位置向后找到第一个满足这个条件的地址作为首地址。
要注意的是,即便采用这个原则,C002得到的结果也应该为13。但是结构体一般会面临数组分配的问题。编译器为了优化这种情况,干脆把它的大小设为14,这样就没有麻烦了。否则的话,会出现单个对象的大小为13,而大小为n的对象数组大小却为14*(n-1)+13的尴尬局面。(如果让你设计编译器,你将怎样解决内存对齐的问题。)
3. 有普通成员变量与普通成员函数的类
class C006
{
int set(){return 1;};
static int foo(){return 2;}
}; // sizeof = 1
class C007
{
short m; // sizeof = 2
float n[3]; // sizeof = 4
int set(){};
static int sfoo() { return 1; }
}; // sizeof = 16
普通成员函数、静态成员函数、静态成员变量皆不会在类的对象中有所表示。
成员函数和对象的关联由编译器在编译时处理,编译器会在编译时决议出正确的普通成员函数地址,并将对象的地址以this指针的方式,做为第一个参数传递给普通成员函数,以此来进行关联。
静态成员函数类似于全局函数,不和具体的对象关联。静态成员变量也一样。静态成员函数和静态成员变量和普通的全局函数及全局变量不同之处在于它们多了一层名字限定。
4. 基类为空且派生类为空
class base
{
}; // sizeof = 1
class derived: public base
{
}; // sizeof = 1
5. 基类为空且派生类不为空
class base
{
}; // sizeof = 1
class derived: public base
{
float f;
char c[11];
int foo(){return 4;};
}; // sizeof = 16
6. 基类不为空且派生类为空
class base
{
float f;
char c[11];
int foo(){return 4;};
}; // sizeof = 16
class derived: public base
{
}; // sizeof = 16
7. 基类不为空且派生类不为空
class base
{
float f;
char c[11];
public:
int foo(){return 4;};
}; // sizeof = 16
class derived: private base
{
float f1;
char c1[11];
public:
int foo1(){return 4;};
}; // sizeof = 32
基类的成员变量悉数被派生类继承,并且与继承方式(公有或私有)无关,如derived是私有继承自base。继承方式只影响数据成员的能见度。派生类对象中属于从基类继承的成员变量由基类的构造函数初始化。通常会调用默认构造函数,除非派生类在它的构造函数初始化列表中显式调用基类的非默认构造函数。如果没有指定,而父类又没有缺省构造函数,则会产生编译错误。
8. 多层继承
class level_1
{
private:
char c[3];
public:
level_1()
{
c[0] = 0x00;
c[1] = 0x01;
c[2] = 0x02;
};
void set()
{
c[0] = 0x00;
c[1] = 0x00;
c[2] = 0x00;
};
}; // sizeof = 3
class level_2: public level_1
{
private:
int f;
public:
level_2()
{
f = 7;
};
void set()
{
f = 0;
};
}; // sizeof = 8
class level_3: public level_2
{
private:
short s[5];
public:
level_3()
{
for(int i = 0;i<5;i++)
s[i] = i;
};
void set()
{
for(int i = 0;i<5;i++)
s[i] = 0x400;
};
}; // sizeof = 20
输出结果为:
The size of level_1 is 3
The detail of level_1 is 00 01 02
The size of level_2 is 8
The detail of level_2 is 00 01 02 cc 07 00 00 00
The size of level_3 is 20
The detail of level_3 is 00 01 02 cc 07 00 00 00 00 00 01 00 02 00 03 00 04 00 cc cc
因此关于多层继承,派生类的对象布局为基类中的数据成员根据内存对齐加上派生类中的数据成员,顶层类在前
9. 多重继承
将8中的例子修改为:
class level_1{…};
class level_2{…};
class level_3: public level_1, public level_2{…};
即只修改继承关系,将多层继承转化为多重继承,实现不变。那么输出结果为:
The size of level_3 is 20
The detail of level_3 is 00 01 02 cc 07 00 00 00 00 00 01 00 02 00 03 00 04 00 cc cc
由此可见,关于多重继承,派生类的对象布局仍为基类中的数据成员根据内存对齐加上派生类中的数据成员,左基类在前
10. 多层继承与多重继承
class L1
{
char c1[2];
public:
L1(){c1[0] = 0x01;c1[1] = 0x02;};
}; // sizeof = 2
class L2
{
int i2[2];
public:
L2(){i2[0] = 7;i2[1] = 8;};
}; // sizeof = 8
class L3:public L1,public L2
{
char c3[2];
public:
L3(){c3[0] = 0x04;c3[1] = 0x05;};
}; // sizeof = 16
class L4:public L2
{
int i4[2];
public:
L4(){i4[0] = 9;i4[1] = 10;};
}; // sizeof = 16
class L5:public L3,public L4
{
char c5;
public:
L5(){c5 = 0x0a;};
}; // sizeof = 36
输出结果为:
The size of L1 is 2
The detail of L1 is 01 02
The size of L2 is 8
The detail of L2 is 07 00 00 00 08 00 00 00
The size of L3 is 16
The detail of L3 is 01 02 cc cc 07 00 00 00 08 00 00 00 04 05 cc cc
The size of L4 is 16
The detail of L4 is 07 00 00 00 08 00 00 00 09 00 00 00 0a 00 00 00
The size of L5 is 36
The detail of L5 is 01 02 cc cc 07 00 00 00 08 00 00 00 04 05 cc cc 07 00 00 00
08 00 00 00 09 00 00 00 0a 00 00 00 0a cc cc cc
在继承层次中某一位置的某派生类,其实例对象的内存中,存储着从左基类开始的各个基类的内存内容,最后是自己的内存内容。这是一个递归的过程。类似于向基类方向宽度优先遍历继承树
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics