目录
C和C++中有两种从名字上看好像很有联系很相似的关键字,分别是结构体(struct),联合体(union)。两者的功能都是可以自定义数据类型,但是深层的原理是不一样的。
结构体
看下如下结构体
struct test{
char a;
int b;
double c;
};
//sizeof(test) = 16
一个test结构体的对象的大小为16字节,而不是我们所认为的13(1+4+8)字节。具体的原因是因为,为了让CPU总线(32位或64位)的读取效率达到最高,减少对内存的读取次数,一般编译器会对结构体的成员进行字节对齐。默认要求符合
以下对齐的规则:
①对齐后的结构体变量的大小须为最宽位成员的整数倍。
②结构体每个成员相对结构体首地址的偏移量(offset)都是每个成员本身大小的整数倍(成员的首地址相对于结构体的首地址的偏移)
③如果成员也是一个结构体,则结构体成员开始存储的偏移要是该结构体成员中最大成员的整数倍。
%=非对齐的字节
#=对齐插入的字节
所以test的成员的大小的真实分布
|a|b|c| ->|%###|%%%%|%%%%%%%%|
偏移从0开始算第一个%的偏移为0。
再来两个例子
struct test2 {
char text[5];
long number1;
long long number2;
};
//sizeof(test2) = 24
struct test3 {
char text[2];
long long number2;
long number1;
};
//sizeof(test3)=24
可以发现,两个例子的大小都是24,why? test3的对象的大小不应该是24吧。
我们下面来分解以下内存的真实分布
test2(很简单)
|text|number1|number2| —-> |%%%%%###|%%%%####|%%%%%%%%|
当char插入5个字节后,因为偏移6~7都不是第二个成员number1的类型long(4字节)的整数倍,所以补齐了三个字节,知道第8个偏移才是number1的数据存放位置。
然后呢因为偏移12~15也不是number2的大小(8字节)的整数倍,所以补齐4个字节,知道偏移16开始才是number2的数据存放位置。
所以最终的大小为:(4+3)+(4+4)+8 = 24。
可以发现也满足对齐规则①
理解了test2后
test3(就要分析一下了)
|text|number2|number1| —-> |%%######|%%%%%%%%|%%%%####|
给char分配完了空间后,因为偏移2~7都不是longlong类型(8字节)的大小的整数倍所以补齐6个字节,在偏移8开始是number2的空间,然后刚好偏移16是long(4字节)的大小的整数倍,所以偏移16开始是存放number1的空间。然后计算一下发现此时结构体的整体大小为:(2+6)+8+4=20。
20无法整除8,不满足规则①,所以会继续补齐,补4个字节,直到结构体的整体大小为24,能够整除8为止。这个就是要注意的了。
如果有成员是一个结构体的话。
struct test2 {
char text[3];
int a;
};
struct test1 {
test2 a;
char text[5];
double number2;
};
//大小为24,内存分布可以按照上面的规则画画。
联合体
联合体就不同了。
- ①它的所有成员相对于基地址的偏移量都为0
- ②空间要大到能容纳最宽的成员
- ③其对齐方式要适合其中所有的成员
对于①,其实就是联合体的所有成员是共享内存的
比如
union test_u{
char a;
int b;
};
//sizeof(test_u) = 4
也就是说,联合体如果对其中任意一个成员赋值,是会发生覆盖的。如果先给a赋值,然后在给b赋值。则a的数据会给覆盖掉。
对于②就没什么问题的了。
但是③呢,就有点意思了,联合体会发生对齐,且要适合全部成员?什么意思
看看下面的例子
union test_u{
char a[9];
int b;
};
//sizeof(test_u) = 12
所以内存的分布为:
|a|b| —> |%%%%%%%%%###|
按照①和②的规则,test_u的大小应该是9字节就足够了,但是实际的大小为12。这是因为9只能整除char(1字节)的大小,而无法整除int(4字节)。所以发生了字节对齐,差多了三个字节,使得test_u的大小为12,以满足③的要求。
在来一个例子
union test_u{
char a[9];
int b;
double c;
};
大小为16字节,根据条件来画画内存的分布呗。