数据对齐方式
数据对齐
数据对齐 (Data Alignment)是指数据在内存中的存放方式,它要求数据的起始地址必须是某个数(通常是 1、2、4、8)的整数倍,这个数被称为 对齐因子 (Alignment Factor)。数据对齐的目的是为了提高 内存访问 的效率,因为许多计算机系统都是按照数据的对齐边界来设计 内存访问 硬件的。
不对齐 的数据访问可能会导致 性能下降,因为处理器可能需要额外的 内存访问 来获取不完整的数据。在一些严格要求 数据对齐 的架构中,不对齐 的数据访问甚至会导致 硬件异常。
在 C11 中,我们可以通过 _Alignof
来查看不同数据类型的 对齐因子:
#include <stdint.h>
#include <stdio.h>
#define EVAL_PRINT(expr) printf("%-20s = %u\n", #expr, (uint8_t)(expr));
int main(void) {
EVAL_PRINT(_Alignof(char));
EVAL_PRINT(_Alignof(uint8_t));
EVAL_PRINT(_Alignof(uint16_t));
EVAL_PRINT(_Alignof(uint32_t));
EVAL_PRINT(_Alignof(int));
EVAL_PRINT(_Alignof(uint64_t));
EVAL_PRINT(_Alignof(void*));
EVAL_PRINT(_Alignof(size_t));
return 0;
}
以上程序运行的结果如下所示,以此我们可以判断每个类型的 aglinment 大小。
$ gcc alignof.c && ./a.out
_Alignof(char) = 1
_Alignof(uint8_t) = 1
_Alignof(uint16_t) = 2
_Alignof(uint32_t) = 4
_Alignof(int) = 4
_Alignof(uint64_t) = 8
_Alignof(void*) = 8
_Alignof(size_t) = 8
以下图中的结构体定义为例,假设我们定义一个 变量,变量的类型长度为 K 个字节,那么这个 变量 在 内存 中的地址 addr 必须是 K 的整数倍,即 addr % K == 0。
上图中 变量 b 和 a 之间增加了 1 个字节的 padding,变量 d 的末尾也增加了 3 个字节 padding,以保证下一个 数据 的开始是 4 的整数倍。
大小端
大小端 (Endianness)是指多字节 数据在内存中的字节序,也就是 字节 的排列顺序。主要有两种存放方式:
大端序也叫做 大端模式 (Big-Endian): 数据内部的 高位字节 存放在 低位地址,低位字节 存放在 高位地址。也就是说,一个整数的第一个字节(最高有效字节)将存放在起始地址处。
小端序也叫做 小端模式 (Little-Endian): 数据内部的 低位字节 存放在 低位地址,高位字节 存放在 高位地址。也就是说,一个整数的最后一个字节(最低有效字节)将存放在起始地址处。
举一个例子,假如定义数组 long a[2] = {0x76543210, 0xFEDCBA98}
,long
类型的大小为 8 字节,数组a
在内存中的起始地址为 0x1000
,则数组中两个元素在内存中的字节排列如下图所示:
理解大小端的核心是要分清 字节内部的 bit 顺序 和 多字节数据的字节顺序(大小端的核心问题)
- 大小端(Endianness) 本身并 不涉及字节内部的位顺序,它只规定 字节之间在内存中的排列方式。
- 在绝大多数现代 CPU 架构中,一个字节(8 bit)的内部位顺序都是 bit7 在高位,bit0 在低位,并且在寄存器和内存访问时,bit 顺序是一致的。
大小端 的选择通常是由计算机的 CPU 架构 决定的,不同的架构有不同的 字节序 要求。例如,Intel x86 和 x86-64 架构是 小端,而网络协议通常是 大端,因为 大端 的格式在 字节流 中的表示更加直观。
最后一句话总结一下 大小端:
- 小端序 是低位字节在低地址
- 大端序 是高位字节在低地址