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-64ARM (可配置), 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] = 0x4567

4. 二进制文件格式

  • 小端序架构(x86):直接读写二进制文件通常是小端序
  • 跨平台二进制格式:通常会指定字节序(如 JPEG 大端序、BMP 小端序)

为什么 x86 使用小端序

历史原因:小端序使算术运算更直观。加法从最低有效字节开始,不必先定位最高位。

# 小端序下,多精度加法:
# 从低地址到高地址依次相加,处理进位即可
addq    (%rdi), %rax     # 先加低位
adcq    8(%rdi), %rdx    # 再加高位(带进位)

链接

    • 第 2 章对字节序的详细讨论
    • 栈上数据的字节序
    • mov 指令对多字节数据的处理

链接到