精简版 · 小杯2026-08 冻结,已停止更新(发布前修订了 4 处已知错误)。后续勘误与新增内容只在正式版。看正式版(中杯)→
Skip to content

数据对齐与大小端

考情分析

典型题型有两类:给定多字节数据和起始地址,问某个字节落在哪个绝对地址;给定结构体成员,算对齐后的总大小、各成员偏移,再据此定位数组中某个元素的某个字节。它也常作为大题里的一小问出现——反汇编中的立即数、位移量同样按字节序摆放。

大纲定位

考纲第四章(四):数据的对齐和大/小端存放方式

这一条在大纲里归属指令系统章,因为字节序是指令集体系结构的属性;本站把文章放在数据表示章,是因为解题的前置知识(补码、类型宽度、机器数的字节摆放)都在这一章。两种归类不冲突。

字节序(Byte Order)

一个多字节数据(如 32 位整数 0x12345678)在内存中按字节存放时,高字节和低字节的排列顺序有两种约定。

大端(Big Endian)

高位字节存放在低地址,与人类阅读顺序一致。

地址0x1000x1010x1020x103
数据12345678

网络协议(TCP/IP)统一使用大端,因此也叫网络字节序

代表架构:IBM z/Series、SPARC、PowerPC。

小端(Little Endian)

低位字节存放在低地址

地址0x1000x1010x1020x103
数据78563412

小端的优势:取低字节时地址不随数据宽度变化,方便硬件做变宽度访问(如从同一地址读 1 字节或 4 字节,最低字节地址相同)。

代表架构:x86/x64、ARM(默认小端)。

对比与记忆

特性大端小端
低地址存放最高有效字节(MSB)最低有效字节(LSB)
人类可读性自然反直觉
网络传输标准字节序需转换
硬件取低字节地址随宽度变化地址固定

口诀:大端 = 高字节在低地址(高低),小端 = 低字节在低地址(低低)。

判断字节序(编程方法)

c
int x = 1;
if (*(char *)&x == 1) {
    // 低地址存的是 1(低字节),小端
} else {
    // 低地址存的是 0(高字节),大端
}

数据的边界对齐

为什么对齐

CPU 按字(4 字节)或双字(8 字节)访问内存。如果一个 4 字节 int 跨越了两个对齐边界,处理器需要两次内存访问再拼接。

对齐规则

k 字节的数据类型存储在 k 的整数倍地址上:

数据类型大小对齐要求
char1B1(任意地址)
short2B2 的倍数
int / float4B4 的倍数
double8B8 的倍数
数组 T[n]n× sizeof(T)= 元素类型 T 的对齐要求(不是数组总长!)
嵌套结构体见下= 其内部成员中最大的对齐要求

编者注(易错)char name[10]对齐要求是 1,不是 10——它占 10 字节,但可以从任意地址开始。把"k 字节对齐到 k"生搬到数组上,是结构体布局题的头号错法。

"最大对齐值"指的是:该结构体所有成员的对齐要求中的最大者(不是成员大小的最大者)。

一条常被忽略的推论

"地址是 k 的倍数"等价于"地址的二进制log2k 位恒为 0":

对齐到地址低位
2 字节末 1 位恒为 0
4 字节末 2 位恒为 00
8 字节末 3 位恒为 000

硬件可以不存这几位。所以"指令按 4 字节对齐、地址 32 位"时,PC 只需 30 位就够——省下的正是恒为 0 的那 2 位。这条推论直接是选择题的采分点。

结构体对齐

结构体的每个成员按自身对齐要求排列,成员之间可能插入填充字节(padding)。整个结构体的总大小是最大对齐值的整数倍。

c
struct S {
    char  a;    // 1B, 偏移 0
    int   b;    // 4B, 对齐到 4 → 偏移 4(填充 3 字节)
    short c;    // 2B, 偏移 8
};
// 总大小:最大对齐 4,8+2=10 → 补齐到 12

内存布局:

偏移01234567891011
内容apadpadpadbbbbccpadpad

调整成员顺序可以减少填充:

c
struct S2 {
    int   b;    // 偏移 0
    short c;    // 偏移 4
    char  a;    // 偏移 6
};
// 总大小:6+1=7 → 补齐到 8

节省了 4 字节,仅靠重新排列成员顺序。

结构体数组:尾部填充是为它存在的

尾部那几个填充字节看着浪费,其实是为了让数组里每个元素都落在对齐边界上。所以:

第 i 个元素的首地址=数组首地址+i×sizeof(struct)

步长就是含尾部填充的 sizeof,不是各成员大小之和。真题的三步链里,这是承上启下的第二步。

未对齐访问的后果

不同架构对未对齐访问的处理不同:

架构处理方式
x86允许但有性能惩罚(自动拆分为多次访问)
ARM(严格模式)产生对齐异常
MIPS必须对齐,否则异常

例题

例 1:32 位整数 0xAABBCCDD 存储在地址 0x1000 处,分别写出大端和小端的存储内容。

大端:

0x10000x10010x10020x1003
AABBCCDD

小端:

0x10000x10010x10020x1003
DDCCBBAA

例 2:小端机器中,short x = 0x0102 存放在地址 0x2000,从 0x2000 读一个字节得到什么?

小端存储:0x2000 放低字节 020x2001 放高字节 01

0x2000 的一个字节,得到 0x02

例 3:计算以下结构体的大小(假设 double 按 8 字节对齐)。

c
struct T {
    char  a;    // 1B, 偏移 0
    double b;   // 8B, 对齐到 8 → 偏移 8(填充 7 字节)
    int   c;    // 4B, 偏移 16
    char  d;    // 1B, 偏移 20
};
// 最大对齐 8, 总大小 20+1=21 → 补齐到 24

答:24 字节。

若把成员重排为 double b; int c; char a; char d;,逐项走一遍:

成员对齐要求偏移占用
double b800–7
int c488–11
char a11212
char d11313

用到 14 字节,最大对齐值仍是 8 → 补齐到 8 的倍数 → 16 字节。比原顺序省了 8 字节。

例 4(反向读数):小端机器,从 0x2000 起连续 4 字节依次是 DC EC FF FF,问该 32 位数是多少?

题目给的是内存物理顺序(低地址在左),不是数值的可读形式。小端下低地址存低字节,所以倒着拼:

0x2000:DC  0x2001:EC  0x2002:FF  0x2003:FF0xFFFFECDC

编者注(易错):顺着读成 0xDCECFFFF 是标准陷阱。判断方向的口诀:小端要倒着拼,大端才顺着拼

考场判序法:不是写 C 程序

上面那段 *(char*)&x == 1 是编程技巧,卷面上用不到。真题给的是真值 + 机器码字节串,让你对照判序。做法是列两行:

:某指令的偏移量真值为 42,32 位补码 = FFFFFFD6H;机器码中该字段的字节依次是 D6 FF FF FF

低地址 → 高地址
机器码里的字节D6 FF FF FF
真值从低位D6 FF FF FF

两行一致 → 小端(若真值要从高位拆才对上,就是大端)。

注意:机器码里的立即数、位移量字段同样按字节序摆放,不只是内存里的数据。反汇编题里的偏移量就是这么读的。

解题动作清单

这类题的标准问法是"给结构体数组的首地址,问某个元素的某个字节在哪个绝对地址"。三步走,一步都不能跳:

① 算出含尾部填充的 sizeof 逐成员定对齐要求(数组按元素类型、嵌套结构体按其内部最大者)→ 排偏移、插填充 → 末尾补齐到最大对齐值的倍数。

② 定位到目标元素

元素首址=数组首址+i×sizeof

③ 在元素内部定位到字节 先加上目标成员在结构体内的偏移,再按字节序在该成员的若干字节里挑——小端时最低有效字节在最低地址。

编者注(卷面):第 ② 步最常被跳过。学生算完 sizeof 就直接在第 0 个元素里找字节,题目问的却是 employee[1]

考点清单

  • [ ] 大端:高字节在低地址(网络字节序)
  • [ ] 小端:低字节在低地址(x86 采用)
  • [ ] k 字节数据对齐到 k 的整数倍地址
  • [ ] 结构体总大小是最大对齐值的整数倍
  • [ ] 成员之间可能有填充字节,调整顺序可减少浪费
  • [ ] 未对齐访问:x86 允许但慢,ARM/MIPS 可能异常

真题练习