Return

ARM 实现字节序反转

Table of Contents

内容均来自嵌入式系统设计课程的课件,只在此作整理。

字节序反转

场景:实现 32 位数据大小端模式的转换。 example

异或交换法

EOR     R1, R0, R0, ROR #16    ; 计算高低位差异
BIC     R1, R1, #0xFF0000      ; 清除 R1 无用位
MOV     R0, R0, ROR #8         ; R0 整体轮转
EOR     R0, R0, R1, LSR #8     ; 再次异或修正

EOR

  • 思路:利用“异或(XOR)”运算的特性来交换数据。
  • 优势:用计算“位差异”避免临时存储空间来保存具体的字节值。
  • 总结:非常数学、精练的写法,代码紧凑。

掩码位移法

MOV R2, #0xFF
ORR R2, R2, #0xFF0000      ; 选中第1、3字节
AND R1, R2, R0             ; 提取 R0 第1、3字节
AND R0, R2, R0, ROR #24    ; R0 右移24位后提取
ORR R0, R0, R1, ROR #8     ; 把 R1 右移8位,与 R0 拼合

Mask

  • 思路:通过掩码(Mask)提取特定位,然后位移拼合。
  • 优势:逻辑清晰(分治法),通用性强,不依赖特定数学特性,可读性高。
  • 总结:适合初学者理解字节操作的基本原理。

拓展

以上的汇编代码是针对“一个 32 位整数内部字节翻转”的场景设计,即大小端转换(BSWAPstd::byteswap)。

在查询资料的过程中,发现 C++ 标准库中有一个通用函数 std::reverse。其兼容性更强,可以用于数组、容器、字符串等任意长度的序列。

因为好奇去了解了一下这个方法的底层实现:

  1. C++ 源码逻辑:双指针 + 交换

    std::reverse 的核心逻辑其实并不复杂,首尾指针(迭代器)不断向中间靠拢并交换内容。

    template<typename BidirectionalIterator>
    void reverse(BidirectionalIterator first, BidirectionalIterator last) {
        // 只要首尾没有相遇
        while ((first != last) && (first != --last)) {
            std::iter_swap(first, last); // 交换两个指针指向的内容
            ++first;                     // 首指针向后移
        }
    }
    • 这里 swap 的标准实现通常是 “中间变量法”,因为在现代 CPU 上,异或交换通常比使用临时寄存器更慢。

      // std::swap 的大概逻辑
      T temp = std::move(a);
      a = std::move(b);
      b = std::move(temp);

      异或交换会产生很强的数据依赖,导致 CPU 流水线冒泡。而使用临时变量可以让 CPU 并行处理。

  2. 编译器优化

    虽然 C++ 源码逻辑是简单的 while 循环,但编译器(GCC/Clang)会根据实际操作的数据类型,把这个循环编译成高效的汇编。

    情况 A:char 数组

    如果代码是 std::reverse(str, str + 4)

    编译器识别出目标操作是一个 4 字节的翻转后不会生成循环,也不会调用 std::swap,而是直接生成一条汇编指令:

    • x86/x64: bswap (Byte Swap)
    • ARM: rev (Reverse bytes)

    这正好对应了“掩码移位法”的最终版。

    情况 B:大型数组

    如果代码是 std::reverse(arr, arr + 1000)

    编译器不会一个一个数去交换,而是使用 SIMD 指令,这是可以直接操作多数据的单条指令:

    1. 一次加载 128 位 或 256 位的数据到向量寄存器。
    2. 使用向量指令(vperm 等)在寄存器内部直接把顺序打乱。
    3. 再一次性存回内存。