我在 ARM7 上做模数时遇到了很多麻烦。
目前,我有这个代码:
ADD R0,R0,R1
MOV R0, R0 MOD 2
BX LR
但它根本不起作用。
从我的同学所做的看来,我们应该通过位移来完成,但我不明白这将如何工作。
确实,您的语法不正确。尽管大多数(全部?)ARM 汇编器都支持MOD
运算符,但它仅在两个操作数都是汇编时常量时才有效。它只是进行汇编时算术和常量表达式折叠。所以,你可以这样做:
mov r0, #11 MOD 3 ; R0 = 2 = (11 % 3)
这基本上会变成:
mov r0, #2
从而将值 2 移动到R0
寄存器中。
这很好,因为它允许您对声明的常量(用于提高可读性)执行取模,并且还可以编写表达式以使它们易于阅读,因此更易于维护。
但是,当您处理寄存器、变量或任何非汇编时常量时,它就不起作用了。
根据您在问题中的代码,您似乎正在将寄存器的内容添加R1
到R0
寄存器中,然后尝试计算R0
模 2。
假设整数是unsigned,这很简单:
add r0, r0, r1 ; R0 = (R0 + R1)
and r0, r0, #1 ; R0 = (R0 & 1)
bx lr
这是有效的,因为x % 2
等效x & 1
于无符号整数。一般来说,只要(除数)是2的幂x % n
就等于。这不仅更容易编写,而且也是一种性能优化,因为按位运算比除法更快。x & (n - 1)
n
既然您知道了以 2 的幂为模的模式,您就可以轻松做到(r0 + r1) % 4
:
add r0, r0, r1 ; R0 = (R0 + R1)
and r0, r0, #3 ; R0 = (R0 & 1)
bx lr
如果你想对一个不是2 的幂的常数进行取模,那么事情会变得更加复杂。我不会尝试在汇编中手动写出来。相反,我会看看编译器会生成什么. 这是您(r0 + r1) % 3
在装配中执行的方式:
add r0, r0, r1 ; R0 = (R0 + R1)
movw r3, #43691 ; \ R3 = 0xAAAAAAAB
movt r3, 43690 ; /
umull r2, r3, r3, r0 ; R3:R2 = (R3 * R0) [R3 holds upper and R2 holds lower bits of result]
lsrs r3, r3, #1 ; R3 = (R3 >> 1)
add r3, r3, r3, lsl #1 ; R3 = (R3 + R3 * 2)
subs r0, r0, r3 ; R0 = (R0 - R3)
bx lr
编译器已生成优化代码来计算整数模数。它没有进行完全除法,而是将其转换为乘以一个幻数(乘法逆运算)。这是Hacker's Delight 的标准技巧,也是许多编译器使用的常见强度降低优化。
到目前为止,我们已经研究了无符号整数类型的模运算。当你想对有符号整数进行模运算时怎么办?好吧,您需要考虑符号位(即 MSB)。
For (r0 + r1) % 2
, wherer0
和r1
被签名,从而r0 + r1
产生一个有符号的结果:
adds r0, r0, r1 ; R0 = (R0 + R1) <-- note "s" suffix for "signed"
and r0, r0, #1 ; R0 = (R0 & 1) <-- same as before for unsigned
it mi ; conditionally execute based on sign bit (negative/minus)
rsbmi r0, r0, #0 ; negate R0 if signed (R0 = abs(R0))
bx lr
这与我们为无符号模数编写的代码非常相似,除了IT
+RSBMI
指令根据输入值是否为负(换句话说,取绝对值)进行条件取反。
(您在问题中只指定了 ARMv7,而不是您的目标配置文件。如果您的芯片具有“A”(应用程序)配置文件,您可以省略该IT
指令。但否则,您的目标是 Thumb-2 指令集,它不会'不支持非分支指令的条件执行,所以你需要IT
在RSBMI
指令之前。参见Thumb-2 中的条件执行。)
不幸的是,计算(r0 + r1) % 4
并不是改变AND
指令的常数操作数的简单问题。你需要更多的代码,即使是对常数的二的幂模。再次,询问编译器如何做到这一点。绝对要向编译器询问非 2 次幂的有符号模数。
如果你想对两个变量进行一般的模运算,事情就困难得多,因为你不能简单地使用位旋转。C 编译器将发出对库函数的调用:
UnsignedModulo(unsigned int i, unsigned int j, unsigned int m):
push {r3, lr}
add r0, r0, r1
mov r1, r2
bl __aeabi_uidivmod
mov r0, r1
pop {r3, pc}
SignedModulo(int i, int j, int m):
push {r3, lr}
add r0, r0, r1
mov r1, r2
bl __aeabi_idivmod
mov r0, r1
pop {r3, pc}
在这里,GCC 分派给__aeabi_uidivmod
无符号__aeabi_idivmod
库函数和有符号模/除的库函数。其他编译器将有自己的库函数。
不要尝试在汇编中手动编写此类代码。这根本不值得努力。如有必要,从 C 编译器的标准库中提取函数,并调用它来完成繁重的工作。(你的老师不希望你这样做。)