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].
CSAPP阅读笔记-struct, union, 数据对齐-来自第三章3.9的笔记-P183-P191的更多相关文章
- CSAPP阅读笔记-数组分配与访问-来自第三章3.8的笔记-P176-P183
这一节比较简单,仅记录几个比较重要的点: 1.C语言允许对指针进行运算,计算出的值会根据该指针引用的数据类型大小进行伸缩. 例子: 其中,xE是数组的起始地址.注意,指针运算时,若最终结果为指针,则指 ...
- CSAPP阅读笔记-gcc常用参数初探-来自第三章3.2的笔记-P113
gcc是一种C编译器,这次我们根据书上的代码尝试着使用它. 使用之前,先补充前置知识.编译器将源代码转换为可执行代码的流程:首先,预处理器对源代码进行处理,将#define指定的宏进行替换,将#inc ...
- CSAPP阅读笔记-栈帧-来自第三章3.7的笔记-P164-P176
1.基本结构: 如上图所示,是通用的栈帧结构.大致分两块,调用者函数P和被调用者函数Q. 对P来说,要做的工作是把传递参数中多于6个的部分压栈,随后把Q返回时要执行的下一条指令的地址压栈. 对Q来说, ...
- CSAPP阅读笔记-汇编语言初探(控制类指令)-来自第三章3.6的笔记-P135-P163
1.正溢出与负溢出: 首先,一个正数与一个负数相加,不可能溢出,因为结果的绝对值一定小于两个加数的绝对值,既然两个加数能合理表示出来,结果一定也能合理表示出来. 其次,正溢出是由于两个很大的正数相加, ...
- CSAPP阅读笔记-变长栈帧,缓冲区溢出攻击-来自第三章3.10的笔记-P192-P204
一.几个关于指针的小知识点: 1. malloc是在堆上动态分配内存,返回的是void *,使用时会配合显式/隐式类型转换,用完后需要用free手动释放. alloca是标准库函数,可以在栈上分配任 ...
- CSAPP阅读笔记-汇编语言初探(算术和逻辑操作类指令)-来自第三章3.5的笔记-P128-P135
1.算术和逻辑操作类指令分四类:加载有效地址,一元操作,二元操作和移位,如下: 2. leaq指令,类似mov指令,它左侧的数看似是给出一个地址,在内存中从给定的地址取操作数,传给右边的目的地.但其实 ...
- Java程序设计(2021春)——第三章类的重用笔记与思考
Java程序设计(2021春)--第三章类的重用笔记与思考 本章概览: 3.1 类的继承(概念与语法) 3.2 Object类(在Java继承最顶层的类) 3.3 终结类和终结方法(只能拿来用,不可以 ...
- CSAPP阅读笔记-汇编语言初探(数据传送类指令)-来自第三章3.2-3.3的笔记-P115-P128
1.如何由机器代码生成汇编代码? objdump -d再加上文件名即可直接在终端看到由反汇编器恢复的汇编代码.注意,文件名并不一定得是.o文件,任何可执行文件都可以. 结果如下: 仅列举了反汇编tes ...
- CSAPP阅读笔记-32位64位的区别--来自第三章引言的笔记--P110
仅从寻址上看,32位和64位机器能寻址的内存空间大小不同. 需要知道的是,计算机系统对存储器作了抽象,程序“认为”内存是一个很大的字节数组,然而实际上它是由多个硬件存储器和操作系统组合起来实现的. 程 ...
随机推荐
- msf、armitage
msfconsole的命令: msfconsole use module :这个命令允许你开始配置所选择的模块. set optionname module :这个命令允许你为指定的模块配置不同的选项 ...
- 练习题。对DOM中document的深刻理解巩固
//window.onload = modTwo; 1.点击单元格内容 弹窗promrt接收值 将接受的值提换单元格内容 2.点击单元格 出现2个按钮 加粗 字体颜色标红 ...
- 【译文】不是所有的 bug 都值得修复的
原文作者:KRISTINE PINEDO 译者:白乐航 欢迎访问网易云社区,了解更多网易技术产品运营经验. 作为软件开发者,您只需要为客户编写和交付出色的产品和功能. 但您也知道软件开发并不总是那么容 ...
- 十九、Node.js-非阻塞IO、异步以及 '事件驱动EventEmitter'解决异步
1.Nodejs 的单线程 非阻塞 I/O 事件驱动 在 Java.PHP 或者.net 等服务器端语言中,会为每一个客户端连接创建一个新的线程而每个线程需要耗费大约 2MB 内存.也就是说,理论上, ...
- linux命令之文件系统管理命令(下)
1.mount:挂载文件系统 该命令可以将指定的文件系统挂载到指定目录(挂载点),在linux中必须先挂载所有的设备,才能够访问,挂载的目录必须事先存在并且最好为空. 参数 说明 -t(常用) 指定挂 ...
- 2.ECMAScript 5.0
JS的引入方式 内接式 <script type="text/javascript"> </script> 外接式 <!--相当于引入了某个模块--& ...
- 936. Stamping The Sequence
You want to form a target string of lowercase letters. At the beginning, your sequence is target.len ...
- 一个简单的HTML病毒分析
一直就想写这篇东西了,仅仅是上班时说要上班,不写.回家后又忙着玩游戏,丢一边去了.如今仅仅好不务正业的开写了.希望头儿不会知道我的blog.哈哈 在非常久之前就对HTML的病毒非常感兴趣了,非常好奇怎 ...
- 使用concat做字符串拼接和数据迁移
作用: 解决一开始数据库建立不合理造成的字段冗余,从而提取部分字段,数据迁移.拼接字符串的功能. 格式: concat(字段1,'间隔符',字段2....) concat_ws('间隔符',字段1,字 ...
- nginx处理高并发请求强于apache
ginx 不同于 Apache2 的一点就是,Nginx 采用单线程,非阻塞,异步 IO 的工作模型. Apache2 对于每一个请求,都会创建一个新进程或线程,会浪费很多内存和 CPU 时间,而 N ...