机构体在内存中的表示
- 结构体和数组类似,声明后在内存空间中顺序储存各个字段
- 结构体在内存中的储存顺序和代码中声明顺序一致,编译器不会自动优化结构体布局,严格按照声明顺序布局
- 编译器生成代码在搜索结构体中的数据时,完全根据结构体首地址,然后计算偏移量找到字段的实际地址。字段名称是无法用来找数据的,它表示的实质上是当前字段相对于首地址的偏移量
链表节点结构体的机器级表示
此例子中,结构体表示的实际是链表的节点
- movslq做了一个符号位扩展,将32位数据扩展成64位数据。i的值是一个32位整数,mov搬运的话只搬运4字节。而rax是64位寄存器,为保证值能够正确复制过去,要用符号位扩展。因此我们将i的值扩展为64位,再加到rax寄存器中。但是为什么目标操作数必须为64位呢?因为rax后边会参与到寻址模式中,而地址是64位的,因此参与地址计算的寄存器必须是64位的。所以前边我们必须要用rax64位寄存器
结构体的字节对齐问题
- 紧凑排列的结构体称为未对齐的结构体,未对齐状态下的结构体很多时候是不被推荐的。真正结构体的布局在分布上要遵循字节对齐原则:对于结构体字段,如某个字段需要k个字节完成存储,那么这个字段所在的地址必须为k的整数倍
- 为何需要字节对齐?
- 冯诺依曼结构中处理器和存储器的数据通道是采用系统总线进行连接的。系统总线包括地址总线、数据总线、控制总线。而在字节对齐问题里,主要讨论总线的宽度问题,尤其是数据总线的宽度问题。对于64位处理器,其数据总线、地址总线的宽度为64位。而数据总线为64位意味着处理器(CPU)要想访问内存的话,一次性会传过来8个字节(如将地址100——107中的数据全部加载到CPU),且CPU不能从非8整倍数的起始地址读取八个字节(如101-108)
- 当数据不对齐时,由于CPU只能从8整倍数的起始地址加载数据,意味着CPU要进行两次内存访问,且最后拼凑起来才能把数据读出来。这还是因为Intel的X86处理器上支持一个访问指令访问8字节数据,但是访问地址不是8的整倍数;此时CPU会自动加载两次内存数据,并在内部把数据拼凑起来。而这种情况在其他很多处理器中都是不允许的。即使能够读取这样的数据,也存在性能问题。由于不对齐要进行两次内存访问才能读取,会大大降低性能
- 而当int型变量以二的整数倍而非4的整数倍为起始地址存储时,尽管在一次访问中即可读取数据。却会造成内存碎片,即其左右空间不能存储int型变量,造成空间上的浪费
- 综上,x86处理器建议使用对齐,否则会使性能下降;其他处理器必须对齐,否则会出错。字节对齐的工作由编译器完成,即编译器编译时针对结构体和当前情况,自动插入保留字节,从而使布局满足字节对齐要求
字节对齐在x86系统中是如何实现的
- X86 64位系统字节对齐规则与上边所述完全相同。简单记法就是1、4、8、16个字节的数据类型的对齐,要求其起始地址的二进制形式的最低1、2、3、4位均为0
- X86 32位平台的1、2、4个字节的数据类型的对齐与64位平台相同(值得注意的是,32位中long、指针类型都是4字节)。但对于8字节(double)与64位平台有所不同,在Windows和其他操作系统中,要求放到8的整倍数的起始地址上去;而linux只要求4的整倍数。
- 32位平台为何对8字节数据类型有特殊要求呢?在X86 32位系统中,数据总线只有32位,因此无论是否8字节对齐还是4字节对齐,都是访问两次,对性能没有任何影响。那么为何Windows 以及其他系统都要求8个字节对齐呢?这样不但没有性能提升,而且会占用更大空间。其实主要为了兼容未来的64位系统。而linux系统主要从占据空间上考衡,因此要求8字节对齐
- 值得注意的是,上边我们讨论的都是X86处理器默认情况下的字节对齐情况。由于在X86处理器中不按规则进行对齐的数据也是可以访问的,所以默认规则不是强制规则,只是这种情况下效率最高。我们在X86编程中甚至可以使用某些关键字或编译选项要求新的字节对齐规则。比如最大到4字节对齐、2字节对齐、不对齐等,这些强制设置字节对齐情况通常在特殊情况下使用
字节对齐的细节问题
起始地址和结束地址
- 起始地址取决于K的值,k是整个结构体中占用空间最多的数据类型的元素的空间宽度。起始地址必须为k的整倍数
- 尾部字节对齐问题一样。结束地址的后一个地址必须满足为k的整倍数
- 这样做主要为了考虑让结构体内所有字段满足字节对齐要求,并且不浪费字节对齐空间。通过对起始地址和结束地址的要求。实现了结构体中每个元素间紧紧相连,同时起始地址和结束地址都符合字节对齐的基本要求。
对结构体数组访问的机器级实现
- movzwl指令:w表示操作2字节数据;l表示操作四字节数据,z表示0拓展;此指令表示从这个地址下取2字节数据(short),填充到32位寄存器中,高16位以0拓展方式填充
如何在程序设计中更好利用空间
把相同数据类型放一起(因为结构体的存储顺序与声明顺序相同,因此声明顺序可能影响储存空间)
联合体
- 联合体是C语言中的特殊语言,其他语言较为罕见。定义方法与结构体类似,但内存布局与结构有很大区别。结构中各字段在内存中以平铺方式布局,各字段在内存中顺序展开;联合体在存储中则是重叠的,各字段共享相同存储空间。union的存储空间取决于最大的数据类型的宽度。
- union可以把数据以double类型存储进去,再通过int型访问double型的低4字节和高4字节数据,通常可以起到一个将数据类型转换的效果。但是与强制类型转换不同,强制类型转换是在值方面进行转换,值是不变的;而由于联合体字段在内存中共享,所以是在位级别进行的转换,二进制不变,值会发生改变。
- union很少有应用场景,较为罕见
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。