PS: 写在前面
字节对齐这玩意我一直没明白到底是,怎么对齐,今天在牛客上的一道题目,让我大概了解了,字节对齐
这文章拖了好久都没完善,最近整理文章才发现
# 理论
先来补充一些理论.
# 什么是字节对齐?
理论上计算机对于任何变量的访问都可以从任意位置开始,
然而实际上系统会对这些变量的存放地址有限制,
通常将变量首地址设为某个数 N 的倍数,这就是字节对齐
简单来说呢就是,变量理论上可以从任何地址开始,但是因为某种原因,事实上变量的地址只能是 某个数 的 n 倍
至于什么原因这就涉及到 为什么要字节对齐
# 为什么要字节对齐
官话就是:
- 为了保证数据存取正常。由于硬件平台限制,并不是所有平台都是支持任何地址的内存存储数据
- 提高 CPU 对内存的访问速度
第一点我就不加细说了.
第二点,大概解释一下,CPU 对于 内存的操作粒度 一般是 N 的整数倍。对于大部分 ARM 架构的 CPU 都是不支持 非字节对齐的数据存储.
凡事都会有例外,所以对应的 x86
架构的 CPU, 是可以支持,非字节对齐的数据存储,但是对于非字节对齐的数据也不是一次读出 4 字节,而是采取多次读取对齐的内存,然后进行拼接
例如,每一次读取 4byte. 如果地址任意存储,存在一个变量 a
, 大小为 2 byte, 位于 0x03~0x04 的上.
当 CPU 需要操作这个变量时,CPU 就要先从 0x00 处读一次数据,取最后一部分,再从 0x04 读取一次数据取出第一个字节,然后合并在一起组成变量 a
这样就会大大降低 CPU 访存的效率.
# 字节对齐的规则
下面的结论均为系统默认对齐规则下进行的:
struct stu1 { | |
char a[18]; | |
double b; | |
char c; | |
int d; | |
short e; | |
}; |
# 结构体中间
各结构体的起始地址按照各个类型变量默认规则进行摆放
char 类型变量除外,char 类型变量一般遵循 2 的倍数地址开始存储.
# 结构体最后
以结构体内最大的变量类型为准.
如果是像 int 类型是 4 个字节的,并且结构体的结尾地址不满足 4 的倍数,则需向离最近的 4 的倍数地址进行补齐;
如果是像 double 类型是 8 个字节,并且结构体的结尾地址不满足 8 的倍数,则需向离最近的 8 的倍数地址补齐;
以此类推……
# 结构体嵌套
子结构体的成员变量起始地址要视子结构体中最大变量类型决定
比如 struct a
含有 struct b
, b
里有 char,int,double
等元素,那 b
应该从 8 的整数倍开始存储
struct stu2 { | |
char x; | |
int y; | |
double z; | |
char v[6]; | |
}; | |
struct stu1 { | |
union u1 { | |
int a1; | |
char a2[5]; | |
}a; | |
struct stu2 b; | |
int c; | |
}; |
# 数组成员
比如 char a[5]
,它的对齐方式和连续写 5 个 char
类型变量是一样的,也就是说它还是按一个字节对齐.
# 含联合体(union)成员
取联合体中最大类型的整数倍地址开始存储
# 思考:为什么 CPU 不从 3~4 直接读取变量 a
当我看到网上的 对字节对齐的解释的时候,说到上面第二点就不说了。我相信肯定不止我一个人想过这样的问题
为什么不从 x 开始 往后读 N 个字节?
其实这取决于 CPU 是否支持非对齐存储,如果处理可以支持不对齐读取,那么对不对齐并不会影响到数据的正确性。但是,还是会影响到处理器将数据读入内粗的速度
对齐访问允许处理器高效地使用其数据总线,对齐访问使得内存子系统可以更有效地工作,减少缓存行的分裂和合并.
不支持非对齐访问可以简化 CPU 的设计,减少硬件复杂性和制造成本。尤其是对微处理器来说
# 参考文献
[1] 【C/C++】内存对齐
大道五十,天衍四十九,人遁其一!