图 1:同一个 0x12345678,两种机器上字节排队方向相反。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。字节序是 C/C++ 面试的常驻题,问题本身简单,但它连着网络编程、文件格式、跨平台序列化一整串工程问题,答出这些连线才算答完。
先定义:字节在多字节类型内部怎么排队。
一个 32 位整数 0x12345678 占连续 4 个字节,地址从 a 到 a+3 升高。大端(big-endian):最高有效字节 0x12 放在最低地址 a,然后依次 34、56、78——和人类从左到右的书写顺序一致,「大头在前」。小端(little-endian)正相反:最低有效字节 0x78 放在最低地址,依次 78、56、34、12。两种只是约定,没有优劣;现实的格局是 x86 与主流 ARM 默认小端,而网络协议历史上统一用大端。
注意两个边界:字节序只对多字节类型有意义,单个字节、字符串的逐字节内容、位运算结果都不受影响;它描述的是「字节」的顺序,不是「位」的顺序,别把它和位序搅在一起。
判断当前机器:指针法和 union 法。
思路一样:放一个已知值的多字节变量,去读它最低地址的那个字节。指针法是把 int 的地址强转成 char* 再读首字节;union 法是让一个 uint32 和一个字节数组共享同一块内存,写 uint32、读字节数组。两种写法都合法且常见,union 版本在 C 里是标准认可的类型双关方式之一。
网络传输为什么要统一字节序。
两台字节序不同的机器直接互发二进制整数,小端发出去的 78 56 34 12 会被大端机器读成 0x78563412,数据直接错乱。所以 TCP/IP 协议族规定网络字节序一律是大端,POSIX 提供了一组转换函数:发送前用 htonl/htons(host to network,32 位/16 位)把主机序转成网络序,接收后用 ntohl/ntohs 转回主机序。在大端机器上这几个函数是空操作,在小端机器上做字节翻转——代码不用关心本机是哪种,函数自己会处理。这也是「跨平台代码先问字节序」的出处:凡是要过网络、进文件格式的多字节数据,要么统一走网络序,要么显式逐字节读写。
编译运行,看看本机是哪种。
用 clang 编译运行(clang -Wall -o endian endian.c && ./endian):
#include <stdio.h>
#include <stdint.h>
#include <arpa/inet.h>
int main(void) {
int x = 1; // 小端机器上内存里是 01 00 00 00
char *p = (char *)&x;
printf("指针法: 最低地址字节 = 0x%02X -> %s\n",
(unsigned char)p[0], p[0] == 1 ? "小端" : "大端");
union { uint32_t u; uint8_t b[4]; } v;
v.u = 0x12345678;
printf("union 法: 各地址字节 = %02X %02X %02X %02X -> %s\n",
v.b[0], v.b[1], v.b[2], v.b[3],
v.b[0] == 0x78 ? "小端" : "大端");
uint32_t net = htonl(0x12345678);
uint8_t *nb = (uint8_t *)&net;
printf("htonl 后内存字节 = %02X %02X %02X %02X(高位在前)\n",
nb[0], nb[1], nb[2], nb[3]);
printf("ntohl 还原 = 0x%08X\n", ntohl(net));
return 0;
}
本机(Apple Silicon,小端)实测输出:
指针法: 最低地址字节 = 0x01 -> 小端
union 法: 各地址字节 = 78 56 34 12 -> 小端
htonl 后内存字节 = 12 34 56 78(高位在前)
ntohl 还原 = 0x12345678
两种判断方法都给出小端;htonl 之后内存里的字节变成了高位在前的大端排列,ntohl 再原样转回——四个函数的对称性一目了然。
图 2:左边看排列,右边走判断流程,网络序一行收尾。
跨平台代码的稳妥写法。
知道了原理,落地就两条路:过网络的多字节字段统一转网络序收发;写文件格式时要么像 PNG、网络协议那样在规范里钉死字节序,要么干脆逐字节拼装——(b[0]<<24)|(b[1]<<16)|(b[2]<<8)|b[3] 这种位运算写法在任何机器上结果都一样,因为位运算作用于「值」而非「存储」,天然免疫字节序。 protobuf 这类序列化框架底层也正是按显式字节规则编码,才把字节序问题从用户面前彻底拿走。
面试现场把答案连成线。
建议的答题路径:先用 0x12345678 举例讲清两种排列,再给出判断代码(指针法为主,union 法作为备选),然后主动延伸到网络字节序与 htonl 一族函数,最后补一句工程边界——单字节数据和位运算不受字节序影响,结构体直接 memcpy 过网络是事故高发区(字节序之外还叠加对齐问题,两者是两个维度)。能讲到「为什么协议选大端只是历史约定,但约定本身比选哪种更重要」,这题就答完整了。
你在抓包或解析二进制文件时被字节序坑过吗?当时是大端当成了小端,还是反过来?欢迎讲讲事故现场。