Appearance
数据对齐与大小端
考情分析
典型题型有两类:给定多字节数据和起始地址,问某个字节落在哪个绝对地址;给定结构体成员,算对齐后的总大小、各成员偏移,再据此定位数组中某个元素的某个字节。它也常作为大题里的一小问出现——反汇编中的立即数、位移量同样按字节序摆放。
大纲定位
考纲第四章(四):数据的对齐和大/小端存放方式。
这一条在大纲里归属指令系统章,因为字节序是指令集体系结构的属性;本站把文章放在数据表示章,是因为解题的前置知识(补码、类型宽度、机器数的字节摆放)都在这一章。两种归类不冲突。
字节序(Byte Order)
一个多字节数据(如 32 位整数 0x12345678)在内存中按字节存放时,高字节和低字节的排列顺序有两种约定。
大端(Big Endian)
高位字节存放在低地址,与人类阅读顺序一致。
| 地址 | 0x100 | 0x101 | 0x102 | 0x103 |
|---|---|---|---|---|
| 数据 | 12 | 34 | 56 | 78 |
网络协议(TCP/IP)统一使用大端,因此也叫网络字节序。
代表架构:IBM z/Series、SPARC、PowerPC。
小端(Little Endian)
低位字节存放在低地址。
| 地址 | 0x100 | 0x101 | 0x102 | 0x103 |
|---|---|---|---|---|
| 数据 | 78 | 56 | 34 | 12 |
小端的优势:取低字节时地址不随数据宽度变化,方便硬件做变宽度访问(如从同一地址读 1 字节或 4 字节,最低字节地址相同)。
代表架构:x86/x64、ARM(默认小端)。
对比与记忆
| 特性 | 大端 | 小端 |
|---|---|---|
| 低地址存放 | 最高有效字节(MSB) | 最低有效字节(LSB) |
| 人类可读性 | 自然 | 反直觉 |
| 网络传输 | 标准字节序 | 需转换 |
| 硬件取低字节 | 地址随宽度变化 | 地址固定 |
口诀:大端 = 高字节在低地址(高低),小端 = 低字节在低地址(低低)。
判断字节序(编程方法)
c
int x = 1;
if (*(char *)&x == 1) {
// 低地址存的是 1(低字节),小端
} else {
// 低地址存的是 0(高字节),大端
}数据的边界对齐
为什么对齐
CPU 按字(4 字节)或双字(8 字节)访问内存。如果一个 4 字节 int 跨越了两个对齐边界,处理器需要两次内存访问再拼接。
对齐规则
| 数据类型 | 大小 | 对齐要求 |
|---|---|---|
char | 1B | 1(任意地址) |
short | 2B | 2 的倍数 |
int / float | 4B | 4 的倍数 |
double | 8B | 8 的倍数 |
数组 T[n] | = 元素类型 T 的对齐要求(不是数组总长!) | |
| 嵌套结构体 | 见下 | = 其内部成员中最大的对齐要求 |
编者注(易错):
char name[10]的对齐要求是 1,不是 10——它占 10 字节,但可以从任意地址开始。把"字节对齐到 "生搬到数组上,是结构体布局题的头号错法。
"最大对齐值"指的是:该结构体所有成员的对齐要求中的最大者(不是成员大小的最大者)。
一条常被忽略的推论
"地址是
| 对齐到 | 地址低位 |
|---|---|
| 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内存布局:
| 偏移 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 内容 | a | pad | pad | pad | b | b | b | b | c | c | pad | pad |
调整成员顺序可以减少填充:
c
struct S2 {
int b; // 偏移 0
short c; // 偏移 4
char a; // 偏移 6
};
// 总大小:6+1=7 → 补齐到 8节省了 4 字节,仅靠重新排列成员顺序。
结构体数组:尾部填充是为它存在的
尾部那几个填充字节看着浪费,其实是为了让数组里每个元素都落在对齐边界上。所以:
步长就是含尾部填充的 sizeof,不是各成员大小之和。真题的三步链里,这是承上启下的第二步。
未对齐访问的后果
不同架构对未对齐访问的处理不同:
| 架构 | 处理方式 |
|---|---|
| x86 | 允许但有性能惩罚(自动拆分为多次访问) |
| ARM(严格模式) | 产生对齐异常 |
| MIPS | 必须对齐,否则异常 |
例题
例 1:32 位整数 0xAABBCCDD 存储在地址 0x1000 处,分别写出大端和小端的存储内容。
大端:
| 0x1000 | 0x1001 | 0x1002 | 0x1003 |
|---|---|---|---|
AA | BB | CC | DD |
小端:
| 0x1000 | 0x1001 | 0x1002 | 0x1003 |
|---|---|---|---|
DD | CC | BB | AA |
例 2:小端机器中,short x = 0x0102 存放在地址 0x2000,从 0x2000 读一个字节得到什么?
小端存储:0x2000 放低字节 02,0x2001 放高字节 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 b | 8 | 0 | 0–7 |
int c | 4 | 8 | 8–11 |
char a | 1 | 12 | 12 |
char d | 1 | 13 | 13 |
用到 14 字节,最大对齐值仍是 8 → 补齐到 8 的倍数 → 16 字节。比原顺序省了 8 字节。
例 4(反向读数):小端机器,从 0x2000 起连续 4 字节依次是 DC EC FF FF,问该 32 位数是多少?
题目给的是内存物理顺序(低地址在左),不是数值的可读形式。小端下低地址存低字节,所以倒着拼:
编者注(易错):顺着读成
0xDCECFFFF是标准陷阱。判断方向的口诀:小端要倒着拼,大端才顺着拼。
考场判序法:不是写 C 程序
上面那段 *(char*)&x == 1 是编程技巧,卷面上用不到。真题给的是真值 + 机器码字节串,让你对照判序。做法是列两行:
例:某指令的偏移量真值为 FFFFFFD6H;机器码中该字段的字节依次是 D6 FF FF FF。
| 低地址 → 高地址 | |
|---|---|
| 机器码里的字节 | D6 FF FF FF |
| 真值从低位拆 | D6 FF FF FF |
两行一致 → 小端(若真值要从高位拆才对上,就是大端)。
注意:机器码里的立即数、位移量字段同样按字节序摆放,不只是内存里的数据。反汇编题里的偏移量就是这么读的。
解题动作清单
这类题的标准问法是"给结构体数组的首地址,问某个元素的某个字节在哪个绝对地址"。三步走,一步都不能跳:
① 算出含尾部填充的 sizeof 逐成员定对齐要求(数组按元素类型、嵌套结构体按其内部最大者)→ 排偏移、插填充 → 末尾补齐到最大对齐值的倍数。
② 定位到目标元素
③ 在元素内部定位到字节 先加上目标成员在结构体内的偏移,再按字节序在该成员的若干字节里挑——小端时最低有效字节在最低地址。
编者注(卷面):第 ② 步最常被跳过。学生算完 sizeof 就直接在第 0 个元素里找字节,题目问的却是
employee[1]。
考点清单
- [ ] 大端:高字节在低地址(网络字节序)
- [ ] 小端:低字节在低地址(x86 采用)
- [ ]
字节数据对齐到 的整数倍地址 - [ ] 结构体总大小是最大对齐值的整数倍
- [ ] 成员之间可能有填充字节,调整顺序可减少浪费
- [ ] 未对齐访问:x86 允许但慢,ARM/MIPS 可能异常