数据对齐方式

中优先级
真题练习
数据对齐大小端 在选择题考查得挺频繁的,但是这两点也比较简单,真正掌握要点了基本不会忘。

数据对齐

数据对齐 (Data Alignment)是指数据在内存中的存放方式,它要求数据的起始地址必须是某个数(通常是 1、2、4、8)的整数倍,这个数被称为 对齐因子 (Alignment Factor)。数据对齐的目的是为了提高 内存访问 的效率,因为许多计算机系统都是按照数据的对齐边界来设计 内存访问 硬件的。

0x00:0x04:0x08:0x0C:0x10:0x00:0x04:0x08:0x0C:0x10:对齐的数据布局charpaddingint (4字节)shortpaddouble (8字节)未对齐的数据布局charintshortdoubleCPU 访问 int1次内存访问CPU 访问 int2次内存访问对齐优势✓ 单次访问获取完整数据✓ 硬件优化,访问速度快未对齐问题✗ 需要多次内存访问✗ 某些架构会触发异常01230123

不对齐 的数据访问可能会导致 性能下降,因为处理器可能需要额外的 内存访问 来获取不完整的数据。在一些严格要求 数据对齐 的架构中,不对齐 的数据访问甚至会导致 硬件异常

不同数据类型具有不同的 对齐因子,这是由处理器架构以及编译器的实现共同决定的。一般来说,对齐因子与数据类型的 字节大小 密切相关,基本规律是:

数据类型的对齐因子通常等于其自身大小(或该架构所支持的最大对齐限制)。


在 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;
}

以上程序在我的 64 位系统中运行的结果如下所示,以此我们可以判断每个类型的 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

在考试中涉及到数据类型的对齐因子时,一般遵循以下规则:

数据类型大小(字节)对齐因子(字节)说明
char11任意地址均可访问,无需额外对齐
short22地址必须是 2 的倍数
int44地址必须是 4 的倍数
float44地址必须是 4 的倍数
double88地址必须是 8 的倍数
long long88地址必须是 8 的倍数
指针(在 64 位系统中)88指针大小与系统位宽有关

以下图中的结构体定义为例,假设我们定义一个 变量,变量的类型长度为 K 个字节,那么这个 变量内存 中的地址 addr 必须是 K 的整数倍,即 addr % K == 0

a
b
b
c
c
c
c
d
struct foo {
char a;
uint16_t b;
char d;
};
int32_t c;

上图中 变量 b 和 a 之间增加了 1 个字节的 padding变量 d 的末尾也增加了 3 个字节 padding,以保证下一个 数据 的开始是 4 的整数倍。

大小端

大小端 (Endianness)是指多字节 数据在内存中的字节序,也就是 字节 的排列顺序。主要有两种存放方式:

大端序
32位整数: 0x12345678字节分解:0x120x340x560x78高位字节低位字节内存存储:0x1000:0x1001:0x1002:0x1003:0x120x340x560x78← 低地址← 高地址大端序特征• 高位字节 → 低地址• 低位字节 → 高地址

也叫做 大端模式 (Big-Endian): 数据内部的 高位字节 存放在 低位地址低位字节 存放在 高位地址。也就是说,一个整数的第一个字节(最高有效字节)将存放在起始地址处。

小端序
32位整数: 0x12345678字节分解:0x120x340x560x78高位字节低位字节内存存储:0x1000:0x1001:0x1002:0x1003:0x780x560x340x12← 低地址← 高地址小端序特征• 低位字节 → 低地址• 高位字节 → 高地址

也叫做 小端模式 (Little-Endian): 数据内部的 低位字节 存放在 低位地址高位字节 存放在 高位地址。也就是说,一个整数的最后一个字节(最低有效字节)将存放在起始地址处。


举一个例子,假如定义数组 long a[2] = {0x76543210, 0xFEDCBA98}long 类型的大小为 8 字节,数组a 在内存中的起始地址为 0x1000,则数组中两个元素在内存中的字节排列如下图所示:

0x10
0x32
0x54
ox76
0x00
0x00
0x00
0x00
0x00
0x00
0x00
0x00
0x76
0x54
0x32
0x10
0x98
0xBA
0xDC
0xFE
0x00
0x00
0x00
0x00
0x00
0x00
0x00
0x00
0xFE
0xDC
0xBA
0x98
0x1000
0x1000
0x1008
0x1008
Little Endian
Big Endian
注意

理解大小端的核心是要分清 字节内部的 bit 顺序多字节数据的字节顺序(大小端的核心问题)

  • 大小端(Endianness) 本身并 不涉及字节内部的位顺序,它只规定 字节之间在内存中的排列方式
  • 在绝大多数现代 CPU 架构中,一个字节(8 bit)的内部位顺序都是 bit7 在高位bit0 在低位,并且在寄存器和内存访问时,bit 顺序是一致的。

大小端 的选择通常是由计算机的 CPU 架构 决定的,不同的架构有不同的 字节序 要求。例如,Intel x86 和 x86-64 架构是 小端,而网络协议通常是 大端,因为 大端 的格式在 字节流 中的表示更加直观。

最后一句话总结一下 大小端

  • 小端序 是低位字节在低地址
  • 大端序 是高位字节在低地址