检测一个字符是否介于两者之间'a'
并且'z'
只需要一对一sub
来cmp
进行范围检查。(有关详细信息,请参阅^= 32 背后的想法是什么,它将小写字母转换为大写字母,反之亦然。)
除了最初是小写字母的字符外,我们可以不修改所有字符。在 ARM 模式下,我们可以轻松地断言存储(如果条件为假,则充当 NOP)。假设 CPU 可以有效地处理这个问题,它不会弄脏没有小写字符的字符串的缓存。(@artless noise 的回答也是这样做的,在到达商店之前跳回循环的顶部。)
.syntax unified
@ call this with address of string in R0
upperString_ARM_mode:
b .Lentry @ start in the middle of the loop. Or put upperString: there instead of here.
.Lloop: @ do {
sub r2, r1, #'a'
bic r1, #0x20 @ clear the lower-case bit in the original
cmp r2, #'z'-'a' @ set flags
it ls @ For Thumb2 compat; assembles to nothing in ARM mode
strbls r1, [r0, #-1] @ strb with LS predicate (Lower-or-Same unsigned <=)
@ store upcased version if (c-'a') <=(unsigned) length of alphabet
.Lentry:
ldrb r1, [r0],#1 @ zero-extending byte load (with post-increment addressing)
tst r1, r1
bne .Lloop @ }while( *p != 0 )
bx lr @ return. (R0 pointing at terminating 0 byte)
@@@ UNTESTED, except for checking that it assembles for both ARM and Thumb-2
@@@ Doesn't work for Thumb-1
除了以 a 开头b .Lentry
,您可以将标签放在循环的中间,因此在循环中间使用startupperString
调用它。bx upperString
(通常函数标签位于函数的顶部,但如果不是,则任何假设将前面的代码视为不同函数的一部分的工具)。
重新安排循环以使条件分支位于底部(并且没有无条件分支)称为“循环旋转”优化;这就是为什么我们必须从中间开始。
不幸的是拇指模式cbnz
只能向前跳转,所以你不能将它用作循环分支。
这个版本的函数在循环中的指令比@artless noise 少(7 对 10),但它们每次都运行。这对于分支预测很有用,但在不太依赖它的简单低端 CPU 上可能会更糟。
这在 ARM 或 Thumb-2 中组装(例如,使用arm-none-eabi-gcc -c -mcpu=cortex-m3
),但不适用于只有 Thumb-1 的 CPU。(例如皮质-m0)。
sub
具有与源不同的目标寄存器,以及那么大的立即数,不适合单个狭窄的 16 位指令,既不是 subs 也不是 sub。寻址模式也不行strb
。[r0, #-1]
sub/cmp 通过 2 条指令完成工作。对于某些情况,您可以使用cmp
/ cmpXX
(带有一些谓词)以某种有用的方式设置标志。但是在这里,cmp r1, #'a'
/cmphs r1, #'z'
会使 LS 条件成立,即使r1<'a'
. 因此,其中一条指令必须是rsbs
反向减法,或者您需要寄存器中的一个常量,这样您就可以在不修改任何寄存器的情况下执行cmp r1, #'a'
/cmphs r2, r1
获得一致的标志条件。
您当然可以使用 NEON SIMD 指令更快地完成此操作,一次 8 或 16 个字节,特别是如果您知道长度而不是还必须搜索终止的 0 字节。有关x86 SSE2 版本,请参阅将 C++ 中的字符串转换为大写。