62、大端与小端:如何判断当前系统的字节序?

字节序这个问题,说白了就是「多字节数据在内存里怎么排队」。

我记得刚入行那会儿,第一次在ARM板子上做串口通信协议解析,明明发送端发的是0x12345678,我这边收到的却是0x78563412。当时我还以为是硬件坏了,折腾了一整天……后来老同事过来看了一眼,说:「小子,你遇到大小端问题了。」

嗯,从那以后,字节序就成了我写底层代码时第一个要确认的事情。

什么是大端和小端?

先看一个32位整数:0x12345678。它在内存中占4个字节:

  • 大端模式(Big-Endian):高位字节存低地址。也就是 0x12 放在最前面。
  • 小端模式(Little-Endian):低位字节存低地址。也就是 0x78 放在最前面。

你想想看,这就像写数字。我们人类习惯从左到右写高位,这是大端思维。但CPU为了计算方便,很多都采用小端——因为低位先取出来做加法,效率更高。

地址大端存储小端存储
addr+00x120x78
addr+10x340x56
addr+20x560x34
addr+30x780x12
核心结论:大端符合人类阅读习惯,小端符合CPU计算习惯。没有谁对谁错,只有平台差异。

如何用C语言判断字节序?

方法其实很简单。我习惯用联合体(union)来判断,因为联合体的所有成员共享同一块内存起始地址。

#include <stdio.h>

int main() {
    union {
        unsigned int  val;    // 4字节
        unsigned char bytes[4]; // 4字节
    } test;

    test.val = 0x12345678;

    if (test.bytes[0] == 0x78) {
        printf("小端模式\n");
    } else if (test.bytes[0] == 0x12) {
        printf("大端模式\n");
    } else {
        printf("未知字节序\n");
    }

    return 0;
}

为什么能这么判断?因为 bytes[0] 就是 val 在内存中的第一个字节。如果第一个字节是低位0x78,那就是小端;如果是高位0x12,那就是大端。

我个人习惯:把这个判断封装成一个宏或者内联函数,放在项目公共头文件里。每次移植代码到新平台,先调用一下,心里有底。

另一种方法:用指针强制转换

如果你不想用联合体,也可以用指针来读第一个字节:

#include <stdio.h>

int main() {
    unsigned int val = 0x12345678;
    unsigned char *p = (unsigned char *)&val;

    if (*p == 0x78) {
        printf("小端\n");
    } else if (*p == 0x12) {
        printf("大端\n");
    }

    return 0;
}

原理一样。把int的地址强转成char*,然后解引用,拿到的就是第一个字节。

常见平台字节序一览

平台/架构字节序
x86 / x86-64小端
ARM(默认)小端
ARM(可配置)支持大端
RISC-V小端
PowerPC大端(可切换)
MIPS大端(可配置)
网络协议(TCP/IP)大端(网络字节序)
我曾经踩过的坑:有一次在STM32上做以太网通信,直接拿收到的网络数据包当int用,结果数值全反了。后来才想起来——网络字节序是大端,而Cortex-M默认是小端。必须用 ntohl() 做转换。

字节序对实际开发的影响

字节序问题主要出现在以下几个场景:

  1. 跨平台数据交换:比如PC和嵌入式设备通过串口或网络通信,双方字节序不同,数据就会乱。
  2. 二进制文件读写:在x86上写了一个二进制文件,拿到ARM上读,如果不做转换,数据全错。
  3. 协议解析:很多通信协议(如Modbus、CANopen)明确规定了字节序,必须按协议来。
  4. 强制类型转换:把char*强转成int*时,字节序会影响最终数值。

SVG:字节序判断流程图

判断当前系统字节序的流程 开始判断字节序 选择方法:union 或 指针强转 赋值 0x12345678 bytes[0] == ? 小端模式 大端模式 注:bytes[0] 指向内存低地址的第一个字节

避坑指南

  • 不要假设平台字节序:哪怕你99%的项目都用小端,也请写一个判断。我曾经接手过一个项目,代码里写死了小端,结果客户要求移植到PowerPC上……嗯,改起来很痛苦。
  • 网络编程一定要用转换函数htonl()htons()ntohl()ntohs() 这四个函数,该用就用,别偷懒。
  • 结构体跨平台传输要小心:结构体成员的对齐和填充也会受字节序影响。我建议用序列化函数,逐字节打包。
  • 联合体判断法在C99之后是标准行为:放心用,不是未定义行为。
一句话总结:字节序是底层开发的基本功。写代码前先搞清楚平台是什么序,能省掉你后面80%的调试时间。

公众号:蓝海资料掘金营,微信deep3321