一句话理解
字节序(Endianness) :一个多字节数据(如 int、float)在内存中按什么顺序存放各个字节。
看低地址放的是"高有效字节"还是"低有效字节":
- 大端序 Big-endian :低地址放高有效字节(MSB)
- 小端序 Little-endian :低地址放低有效字节(LSB)
口诀:大端"高低对应",小端"低低对应"------都看低地址。
经典例子
32 位整数:
c
uint32_t x = 0x12345678;
其中:
- 最高有效字节 MSB =
0x12 - 最低有效字节 LSB =
0x78
假设从地址 0x1000 开始存放:
| 地址 | 大端序 | 小端序 |
|---|---|---|
| 0x1000 低地址 | 12 |
78 |
| 0x1001 | 34 |
56 |
| 0x1002 | 56 |
34 |
| 0x1003 高地址 | 78 |
12 |
所以:
- 大端内存:
12 34 56 78 - 小端内存:
78 56 34 12
注意:大小端只影响多字节对象内部字节的排列 ,不影响数组元素顺序,也不影响字符串 "AB" 的 ASCII 字节 41 42。字节内部的位顺序通常也不因此改变。
为什么会有大小端?
主要是 CPU 设计选择和历史原因:
- 大端:内存里的字节顺序和人写数字的顺序一致,阅读调试直观。
- 小端:低地址就是低字节,做低位截断、类型转换、某些算术访问更方便。
现代常见情况:
- x86 / x86_64:小端
- 常见 ARM:通常小端,但架构支持可配置
- RISC-V:常见小端
- 网络协议:规定使用大端,即"网络字节序"
- Java
ByteBuffer默认大端;JVM class 文件采用大端 - PNG 等部分文件格式规定大端;ELF 文件头里有字段标识端序
如何判断本机字节序
简单 C 代码:
c
#include <stdint.h>
#include <stdio.h>
int main(void) {
uint16_t x = 0x0001;
unsigned char *p = (unsigned char *)&x;
if (p[0] == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
原因:0x0001 的低字节是 0x01,高字节是 0x00。
- 小端:低地址放
01,所以p[0] == 1 - 大端:低地址放
00,所以p[0] == 0
也可以用联合体判断,但工程上更推荐用 memcpy 避免别名/对齐争议。
网络字节序
TCP/IP 协议中的多字节字段规定为大端序 ,称为网络字节序。
C 中常用转换函数:
c
#include <arpa/inet.h> // Linux/Unix
// Windows 用 winsock2.h
uint16_t net_port = htons(8080); // host to network short,16 位
uint32_t net_addr = htonl(0x7F000001); // host to network long,32 位
uint16_t host_port = ntohs(net_port);
uint32_t host_addr = ntohl(net_addr);
记忆:
htons/htonl:主机序 → 网络序ntohs/ntohl:网络序 → 主机序- 如果主机本来就是大端,这些函数通常不改变字节;小端主机会交换字节
注意:"网络字节序 = 大端"是通用规定,但具体协议仍要看协议文档 。64 位转换可能需要 htobe64 / be64toh 或自己实现。
实际开发中的坑
-
不要直接把结构体
memcpy到网络或文件- 结构体可能有填充字节、对齐差异
- 字段端序可能不同
- 应逐字段按协议显式序列化
-
跨平台二进制文件要明确端序
- 文件格式规范说大端就按大端读写
- 例如读 PNG 的 4 字节长度字段,要按大端解析
-
文本协议通常不涉及多字节整数端序
- 单字节 ASCII 不受影响
- 但 UTF-16 等多字节编码有端序问题,常用 BOM 标识
-
调试器看到"反了"很常见
- 在 x86 小端机上,
0x12345678的内存窗口常显示为78 56 34 12
- 在 x86 小端机上,
-
位域布局、联合体类型双关与端序/编译器实现有关
- 不要依赖它们做跨平台二进制协议
总结
- 大端:低地址放高字节,内存顺序像人读数字。
- 小端:低地址放低字节,x86 常见。
- 网络字节序是大端,跨主机通信要转换。
- 跨平台数据交换必须显式规定端序,不要假设对方和自己一样。
- 判断口诀:看低地址放的是高字节还是低字节。