CSAPP阅读笔记-struct, union, 数据对齐-来自第三章3.9的笔记-P183-P191
1.数据对齐
为什么要对齐:通俗点解释就是CPU对数据访问时,每次都是取固定数量的字节数,假如一次取4个字节,若有个int存在0x01-0x04,则一次就能取出,若存在0x03-0x06,则需要分两次才能取到(第一次0x01-0x04,第二次0x05-0x08),这样会降低CPU效率,更何况还有像short,char之类的不是4个字节的数据。因此,编译器会对数据进行强制对齐。
对齐规则:
1.任何K字节的基本对象的地址必须是K的倍数
2.在结构末尾根据需要会做一些填充,使其一旦被拓展为数组时可以满足条件1
例子:
struct S1
{
int i;
char c;
int j;
};
分析:
i起始地址偏移量为0,满足条件1,占用4个字节,c起始地址偏移量为4,满足条件1,占用1个字节,本来j起始地址偏移量应该为5,然而为了满足条件1,编译器会在c后面填充3个字节,使j起始地址偏移量为8,另外j占用4个字节,所以S1一共占用12个字节。再看一下是否满足条件2:一旦声明了 struct S1[2],第二个结构体的第一个元素i的起始地址偏移量为12,满足条件1,因此S1一共占用12个字节没问题。
再来一个例子:
struct S2
{
int i;
int j;
char c;
};
分析:
i起始地址偏移量为0,满足条件1,占用4个字节,j起始地址偏移量为4,满足条件1,占用4个字节,c起始地址偏移量为8,满足条件1,占用1个字节,所以S2一共占用9个字节。再看一下是否满足条件2:一旦声明了 struct S2[2],第二个结构体的第一个元素i的起始地址偏移量为9,不满足条件2,因此结构体需在c后面填充3个字节,这样第二个结构体的第一个元素i的起始地址偏移量变为12,满足条件2,综上S2一共占用12个字节。
看起来似乎没问题,但其实上面的分析有个误区,下面通过一道例题来展现:
A,B,C,D都没问题,但E中,按之前的分析,a的起始地址应该是0,每个P3结构体占10个字节,P2结构体的第一个元素是int,所以t的起始地址应该是4的倍数,因此起始地址为20没问题,因为P2占16个字节,所以P5总大小为36个字节。
然而,答案是t的起始地址为24,P5总大小为40个字节。
仔细查看书前面的描述,书里并没有清晰地描述过第二条结论,我觉得第二条自己的总结有误,现在修正为:
2.在结构末尾根据需要会做一些填充,使其一旦被拓展为数组时可以满足条件1,而且拓展时是以数组中下一个结构体元素中数据大小最大那个元素为标准来对齐的。
现在重新来看E,a的起始地址为0,每个P3占10个字节,所以a[0]先占10个字节,又因为P3中最大的元素是short,因此a[1]起始地址为10,符合条件2,此时t的起始地址本应是20,但P2中最大的元素为long,因此要按8字节对齐,所以t的起始地址为24,P2占16个字节,因此P5一共占40个字节。
我们使用新修改的结论来看S2那个例子,发现也没有问题,之所以之前没看出问题,那个例子中最大的元素大小刚好和第一个元素i大小相同,所以问题没有暴露。
另外,根据之前的几个例题,很明显可以看出,结构体中元素排布的顺序不同,会直接影响浪费的空间大小,若要最小化浪费的空间,按从大到小的顺序来排即可,自己思考下就明白了,相应例题可以看习题3.45。
2.union和struct
它们的区别其实很简单,struct类似数组,把不同类型的元素按顺序存储在内存中一段连续区域中,而union则是所有元素起始地址都相同(当然,若某个元素是数组,数组中所有元素仍是顺序排列的),因此union的大小等于其最大元素的大小。
什么时候使用union呢?
当我们知道某结构中的元素是互斥使用时,就可以把它声明成union来节约空间。
例子:
node_t一共占用了24个字节的空间。
分析:
首先,枚举类型本质上是一组常数的集合体,只是这些常数有各自的命名。由于枚举变量的赋值,一次只能存放枚举结构中的某个常数。所以枚举变量的大小,实质是常数所占内存空间的大小(常数为int类型,当前主流的编译器中一般是32位机器和64位机器中int型都是4个字节),枚举类型所占内存大小也是这样。
以上这段话引用自https://blog.csdn.net/bulebin/article/details/54388735
好了,枚举变量type占4个字节,union变量info里最大数据类型为8个字节,因此需要8字节对齐,编译器在type后面填充4个字节,然后union中internal结构体和data数组起始地址相同,且都占16个字节,因此node_t一共占用8+16=24个字节。
最后要注意的一点是union中的字节序:
例子:
本书前几章提到过小端法机器的特点是高字节存高地址,低字节存低地址,以小端法机器为例来看本例:
显然u数组一共占8个字节,且起始地址和d相同,按照小端法来看,那么d的低4个字节存的是u[0],高4个字节存的是u[1].