本地资料数据对齐方式
SCHEDULE LOCAL5 个小节覆盖真题 20122025
关联考点大小端7数据对齐3做相关真题 · 8 道 →
做相关真题 · 8 道选中文字可高亮或加下划线
选中文字高亮 · 下划线

数据对齐方式

复习提示(中优先级):对齐问题关注“起始地址是否满足倍数关系”,大小端问题关注“多字节数据的字节如何映射到递增地址”。

真题练习

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

数据对齐

数据对齐 (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;
}

以上程序在我的 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

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

数据类型 大小(字节) 对齐因子(字节) 说明
char 1 1 任意地址均可访问,无需额外对齐
short 2 2 地址必须是 2 的倍数
int 4 4 地址必须是 4 的倍数
float 4 4 地址必须是 4 的倍数
double 8 8 地址必须是 8 的倍数
long long 8 8 地址必须是 8 的倍数
指针(在常见 64 位系统中) 8 8 指针大小与 ABI 和系统位宽有关

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

结构体成员之间和末尾填充字节保证各成员对齐

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

大小端

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

大端序

数值 0x12345678 按大端序从低地址到高地址依次存放高位字节

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

小端序

数值 0x12345678 按小端序从低地址到高地址依次存放低位字节

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


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

数组中两个八字节 long 元素在大端序和小端序内存中的字节排列

注意:理解大小端的核心是分清 字节内部的 bit 顺序多字节数据的字节顺序。大小端讨论的是后者。

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

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

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

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

对齐、填充与字节序的边界

若某对象要求按 qq 字节对齐,它的起始地址通常应满足

Addrmodq=0.\operatorname{Addr}\bmod q=0.

例如一个要求 4 字节对齐的字在地址 0x1000、0x1004 等位置满足对齐,在 0x1002 则不满足。对齐要求来自处理器与总线按字、按双字等粒度取数的实现:有的体系结构对未对齐访问直接产生异常,有的可拆成多次访存完成但可能更慢。题目没有给出具体体系结构时,应先说明未对齐的后果依实现而异,而不是绝对断言一定出错。

结构体的成员之间可能插入填充字节,使后续成员满足其自身对齐要求;结构体末尾也可能补齐,以便结构体数组中每个元素的起始地址仍正确对齐。因此成员大小相加不一定等于结构体大小。数组元素则按元素类型的固定步长连续排列,访问第 ii 个元素的地址是基址加 ii 个元素大小;对齐只改变元素的起始基址和可能的结构体布局,不改变数组下标的逻辑顺序。

大小端只约束一个多字节对象的字节在连续地址中的先后,既不改变单个字节内 bit 的高低位,也不改变寄存器中数值本身。网络字节序常规定为大端;跨平台传输多字节整数时,发送和接收双方必须以同一字节序解释,必要时在边界处转换。以字节为单位查看字符数组时通常不会暴露大小端差异,只有把同一组字节合并解释成多字节整数或浮点数时才会出现不同结果。