ARM 实现字节序反转
Table of Contents
内容均来自嵌入式系统设计课程的课件,只在此作整理。
字节序反转
场景:实现 32 位数据大小端模式的转换。
异或交换法
EOR R1, R0, R0, ROR #16 ; 计算高低位差异
BIC R1, R1, #0xFF0000 ; 清除 R1 无用位
MOV R0, R0, ROR #8 ; R0 整体轮转
EOR R0, R0, R1, LSR #8 ; 再次异或修正
- 思路:利用“异或(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)提取特定位,然后位移拼合。
- 优势:逻辑清晰(分治法),通用性强,不依赖特定数学特性,可读性高。
- 总结:适合初学者理解字节操作的基本原理。
拓展
以上的汇编代码是针对“一个 32 位整数内部字节翻转”的场景设计,即大小端转换(BSWAP 或 std::byteswap)。
在查询资料的过程中,发现 C++ 标准库中有一个通用函数 std::reverse。其兼容性更强,可以用于数组、容器、字符串等任意长度的序列。
因为好奇去了解了一下这个方法的底层实现:
-
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 并行处理。
-
-
编译器优化
虽然 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 指令,这是可以直接操作多数据的单条指令:
- 一次加载 128 位 或 256 位的数据到向量寄存器。
- 使用向量指令(
vperm等)在寄存器内部直接把顺序打乱。 - 再一次性存回内存。
- x86/x64: