yuqi-zheng

C++ 中的字节序反转:位运算 vs. 编译器内建函数


在网络编程、二进制文件解析和硬件接口等场景中,大端(big-endian)与小端(little-endian)表示之间的转换是一项常见任务。C++ 提供了两种自然的实现方式:手动位运算和编译器内建函数。观察生成的汇编代码可以发现,两者是等价的 —— 但其中一种的可读性要高得多。


手动实现

对于 32 位整数,字节反转需要将四个字节分别移动到相反的位置:

void reverse32(unsigned int* p) {
    unsigned int& a = *p;
    a = ((a & 0xff000000) >> 24)
      | ((a & 0x00ff0000) >>  8)
      | ((a & 0x0000ff00) <<  8)
      | ((a & 0x000000ff) << 24);
}

64 位版本则扩展为八个字节:

void reverse64(unsigned long long* b) {
    unsigned long long& a = *b;
    a = ((a & 0xff00000000000000ULL) >> 56)
      | ((a & 0x00ff000000000000ULL) >> 40)
      | ((a & 0x0000ff0000000000ULL) >> 24)
      | ((a & 0x000000ff00000000ULL) >>  8)
      | ((a & 0x00000000ff000000ULL) <<  8)
      | ((a & 0x0000000000ff0000ULL) << 24)
      | ((a & 0x000000000000ff00ULL) << 40)
      | ((a & 0x00000000000000ffULL) << 56);
}

代码虽然正确,但十分冗长。真正的意图被淹没在八次掩码和移位操作之中。


编译器内建函数

GCC 和 Clang 提供了 __builtin_bswap32__builtin_bswap64,可以直接表达意图:

void reverse32_builtin(unsigned int* p) {
    *p = __builtin_bswap32(*p);
}

void reverse64_builtin(unsigned long long* p) {
    *p = __builtin_bswap64(*p);
}

汇编输出

编译器能识别这两种模式,并为四个版本都生成相同的 bswap 指令:

32 位:

reverse32:
    mov     eax, DWORD PTR [rdi]
    bswap   eax
    mov     DWORD PTR [rdi], eax
    ret

reverse32_builtin:
    mov     eax, DWORD PTR [rdi]
    bswap   eax
    mov     DWORD PTR [rdi], eax
    ret

64 位:

reverse64:
    mov     rax, QWORD PTR [rdi]
    bswap   rax
    mov     QWORD PTR [rdi], rax
    ret

reverse64_builtin:
    mov     rax, QWORD PTR [rdi]
    bswap   rax
    mov     QWORD PTR [rdi], rax
    ret

三条指令。两种写法之间没有任何性能差异。


建议

优先使用 __builtin_bswap32 / __builtin_bswap64,而非手写的位运算。编译器能识别并优化两者,但内建函数让意图更加明确。

使用固定宽度的类型。 推荐使用 uint32_tuint64_t 而不是 unsigned intunsigned long long,以保证在不同平台上宽度一致。

在 C++23 中,使用 std::byteswap 这是标准、类型安全、可移植的写法:

#include <bit>
uint32_t x = std::byteswap(value);

手动位运算版本对于理解硬件行为是有价值的练习,但在生产代码中它只会增加噪音,而既不提升正确性,也不提升性能。