数据对象
谈及的内存对齐, 而在进行这个话题之前, 线引入一个叫数据对象的概念.
数据对象(Data Object), 简称对象, 像基于C衍生出来其他搞成语言所理解的”对象”是有所区别的, C中的对象更偏向于内存模型, 同样C中的数据对象也使用于汇编, 本来C就是”结构化”的汇编语言, 数据对象就是本身两个属性.
- 数据值(value)
- 存储地址(storage location)
也就是C中支持的所有数据类型定义出来的变量或由基本数据类型组合构造的用户定义类型(类类型/也叫结构体), 都统称数据对象, 一切类型皆为对象. 而被内存对齐的正是数据对象.
内存对齐也叫字节对齐(data aligment): 就是数据对象的内存大小可用被2的N次方的整数整除, 也就是说字节对齐可用用某个2的N次方的整数去对齐.
目前计算机的32位的CPU可以在每个时刻周期从内存读取4个字节并填充数据总线, 而64位的CPU每个时刻周期可以读取8个字节, 而C语言的设计者位了遵顼CPU的这种特性, 就是C编译器, 当然后来的C++编译器也继承这一特性, 在对C/C++源码的时候会对源码中的数据对象自动执行内存对齐朝着(对数据对象之前填充一些没用的字节块)
为什么要内存对齐?
因为我们要访问物理内存并能够在一次访问中获取整个数据, 所以想象一下我们要从内存中读取一个4字节的int,而前两个字节在内存中的一个字中, 而后两个字节在另外一个字中. 我们不想分两次读取两个字然后将字节读取的字节再次的字节再次拼装成一个整数, 这是有一种低效的内存访问.
低效的内访问演示(未对齐的Data CPU多次加载word示例)

这是是位于half-word边界上对齐的未对齐word. 为了对此进行操作, CPU必须做量word加载. 一个用于上half-word, 一个用于下half-word. 这两项加载操作将进入两个独立的寄存器. 然后分别将高层我word执行位移量字节向上(同理位于底层地址的word执行项底层地址位移两字节, 上层half-word让后低层half-word寄存器组合). 这需要大约额外的操作来于未对齐的内存数据进行交互, 之时未了进入寄存器进行处理. 与一次性加载指令相比, 可以看出这是一个很大的CPU开销.
因此为了有效地一次性执行内存访问: 直接读取四个字节或八个字节. 就有了内存对齐这个玩意了.
内存对齐规律
- 一般来说, 对于需要X字节的原始数据类型, 地址必须是X的倍数.
- struct的起始地址决于其成员变量中的数据类型的sizeof()最大值作为对齐条件.
- 编译器子啊编译阶段对struct中未使用的内存空间执行填充操作, 以确保字段对齐.
- char类型不需要对齐, 可自由分配任意可用的1字节尺寸的内存空间之中.
基本数据类型的对齐要求
在不同的硬件架构和OS上执行的内存对齐是不一样, 我们下面有个表.
需要特别指出的是
- 对于char类型没用任何对齐要求
- 对于double类型即便是IA32的硬件架构, 事实上可以通过gcc编译的时候指定命令行选项-malign-double, double类型也会以8字节对齐而不是4字节对齐.
- 从这个表可知, 不同类型的CPU, 对齐操作是不一样的.
x86_64环境下的对齐示例
以下示例的