3.22 字节序
字节序(endianness)定义多字节数据在内存中的字节排列顺序。不同的处理器架构采用不同的字节序。
小端序(Little Endian)
最低有效字节存储在最低地址。x86-64 使用小端序。
示例
int x = 0x01234567; // 4 字节整型内存地址增长方向 $→$(从左到右)
小端序存储:
地址: 0x1000 0x1001 0x1002 0x1003
字节: [0x67] [0x45] [0x23] [0x01]
↑ LSB ↑ MSB
读取顺序:从低地址到高地址依次为 67 45 23 01(与书写习惯相反)。
64 位示例
long y = 0x0123456789ABCDEF;小端序:
地址: +0 +1 +2 +3 +4 +5 +6 +7
[EF][CD][AB][89][67][45][23][01]
大端序(Big Endian)
最高有效字节存储在最低地址。网络协议使用大端序(网络字节序)。
示例
int x = 0x01234567;大端序存储:
地址: 0x1000 0x1001 0x1002 0x1003
字节: [0x01] [0x23] [0x45] [0x67]
↑ MSB ↑ LSB
读取顺序:01 23 45 67(与书写习惯一致)。
两种字节序对比
| 特性 | 小端序 (LE) | 大端序 (BE) |
|---|---|---|
| 存储方式 | LSB 在低地址 | MSB 在低地址 |
| 人类阅读 | 与书写顺序相反 | 与书写顺序一致 |
| 使用架构 | x86, x86-64 | ARM (可配置), MIPS, SPARC, PowerPC |
| 网络协议 | — | TCP/IP 协议栈 |
| 调试体验 | 低地址看低位字节 | 低地址看高位字节 |
检测字节序
C 代码检测
#include <stdio.h>
int main() {
int x = 1; // 0x00000001
unsigned char *p = (char *)&x;
if (p[0] == 1) {
printf("小端序 (Little Endian)\n");
} else {
printf("大端序 (Big Endian)\n");
}
// 详细查看
printf("int 1 的内存表示: ");
for (int i = 0; i < sizeof(int); i++) {
printf("%02x ", p[i]);
}
printf("\n");
return 0;
}x86-64 输出:
小端序 (Little Endian)
int 1 的内存表示: 01 00 00 00
指针类型转换的字节序影响
int x = 0x01234567;
char *p = (char *)&x;
// 小端序上:
printf("%x", p[0]); // 输出 0x67 (LSB)
printf("%x", p[3]); // 输出 0x01 (MSB)
// 大端序上:
printf("%x", p[0]); // 输出 0x01 (MSB)
printf("%x", p[3]); // 输出 0x67 (LSB)字节序的实际影响
1. 网络编程
网络字节序为大端序,本地字节序取决于架构。使用转换函数:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //主机$→$网络 (32位)
uint32_t ntohl(uint32_t netlong); //网络$→$主机 (32位)
uint16_t htons(uint16_t hostshort); //主机$→$网络 (16位)
uint16_t ntohs(uint16_t netshort); //网络$→$主机 (16位)// 正确的网络编程实践
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080); // 端口号转为网络字节序
inet_pton(AF_INET, "192.168.1.1", &addr.sin_addr);
bind(sock, (struct sockaddr *)&addr, sizeof(addr));2. 数据序列化
跨平台数据传输需要处理字节序:
// 可移植的序列化方式
void serialize_int(uint8_t *buf, uint32_t val) {
$buf[0] = (val >> 24) & 0xFF; // MSB →$低地址 (网络字节序)
buf[1] = (val >> 16) & 0xFF;
buf[2] = (val >> 8) & 0xFF;
buf[3] = val & 0xFF;
}
uint32_t deserialize_int(const uint8_t *buf) {
return ((uint32_t)buf[0] << 24) | // 高位
((uint32_t)buf[1] << 16) |
((uint32_t)buf[2] << 8) |
((uint32_t)buf[3]); // 低位
}3. 强制类型转换的风险
int x = 0x01234567;
short *p = (short *)&x;
// 小端序上:p[0] = 0x4567, p[1] = 0x0123
// 大端序上:p[0] = 0x0123, p[1] = 0x45674. 二进制文件格式
- 小端序架构(x86):直接读写二进制文件通常是小端序
- 跨平台二进制格式:通常会指定字节序(如 JPEG 大端序、BMP 小端序)
为什么 x86 使用小端序
历史原因:小端序使算术运算更直观。加法从最低有效字节开始,不必先定位最高位。
# 小端序下,多精度加法:
# 从低地址到高地址依次相加,处理进位即可
addq (%rdi), %rax # 先加低位
adcq 8(%rdi), %rdx # 再加高位(带进位)链接
-
- 第 2 章对字节序的详细讨论
-
- 栈上数据的字节序
-
- mov 指令对多字节数据的处理
链接到
- 上一个知识点:3.21 缓冲区溢出
- 下一个知识点:无(本章最后一个知识点)
- 相关知识点:2.1 字与字节序