我在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
register,然后尝试计算R0
模2。
假设整数是无符号的,就像这样简单:
add r0, r0, r1 ; R0 = (R0 + R1)
and r0, r0, #1 ; R0 = (R0 & 1)
bx lr
这是有效的,因为x % 2
相当于无符号整数的x & 1
。一般来说,x % n
相当于x & (n - 1)
,只要n
(除数)是2的幂。这不仅更容易编写,而且还是性能优化,因为按位操作比分区更快。
现在您已经知道了2的幂的模数模式,您可以轻松地执行(r0 + r1) % 4
:
add r0, r0, r1 ; R0 = (R0 + R1)
and r0, r0, #3 ; R0 = (R0 & 1)
bx lr
如果你想用一个不是2的幂的常数来模数,那么事情变得更复杂。我不会试图在集会中手工写出来。相反,我会期待see what a compiler would generate。这是你在汇编中执行(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
编译器已生成优化代码以计算整数模数。它没有进行完全除法,而是通过幻数(乘法逆)将其转换为乘法。这是a standard trick from Hacker's Delight和a common strength-reduction optimization used by many compilers。
到目前为止,我们已经研究了无符号整数类型的模运算。当你想对有符号整数进行模运算时怎么办?那么,您需要考虑符号位(即MSB)。
对于(r0 + r1) % 2
,r0
和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
。请参阅Conditional Execution in Thumb-2。)
不幸的是,计算(r0 + r1) % 4
不是改变AND
指令的常量操作数的简单问题。你需要更多的代码,即使对于两个常量的模数也是如此。再次,ask a compiler怎么做。绝对ask a compiler签署两个非权力的模数。
如果你想对两个变量进行一般模数运算,事情要困难得多,因为你不能简单地使用bit-twiddling。 C compilers are going to emit a call to a library function:
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
库函数用于unsigned,__aeabi_idivmod
库函数用于签名模/分。其他编译器将拥有自己的库函数。
不要在程序集中手动编写这种代码。它根本不值得努力。如有必要,从C编译器的标准库中提取函数,并调用它来完成繁重的工作。 (你的老师不希望你这样做。)