我认为在ARM上,无论寄存器输入如何,通过单个算术指令写入所有四个标志位,都无法得到V=1 C=0且结果为正的情况(Z=0 N=0)。因此,@domen的答案可能是除了直接使用
msr
设置CPSR之外,我们能做的最好的方法,它使用移位来清除C和其他标志位,同时保持V不变。
一般情况下,问题中允许使用算术运算,但是
mul
不会修改V,而
mls
/
mla
/
smull
等指令也不会设置标志位。类似
lsl
的移位指令以及
and
等位操作指令也不会修改V。当
sdiv
溢出时,例如
INT_MIN/-1
,不会留下任何痕迹:
sdiv
根本不会设置标志位。
加法和减法会设置所有四个标志位,但是如果你将减法看作是实际的二进制减法,那么减法会将C设置为非借位输出。(ARM的减法x - y
会将Carry标志位设置为从带进位加法x + ~y
中得到的,其中carry-in = 1。)
- 我们需要结果非零,所以要清除Zero标志位。
- 我们需要结果非负,所以要清除Negative标志位,因此我们需要结果为正数。
- 我们需要Carry标志位清除,所以我们不能进行
负数 + 负数 => 正数
的加法运算;它们的符号位会进位到C。而正数 + 正数
溢出为负数会设置N。
所以我们不能使用加法。
减法可以带符号溢出,例如正数减去负数得到负数,这是我们不想要的。
或者负数减去正数得到正数。但是负数比正数更大,所以这些减法不会产生借位。因此它们确实设置了C。(当C被设置且Z被清除时,
bhi
会跳转。)
因此,减法的溢出条件都无法避免设置C。(至少在不考虑
adc
/
sbc
的情况下,不确定它们是否有帮助。)
以下是一种相当简洁高效的方法:首先使用
subs
进行减法,它会设置除了
N
以外的所有标志位,然后进行一次移位,覆盖除了
V
以外的所有标志位。
.syntax unified
movs r0, #0x80 // fits in a Thumb 16-bit mov reg, imm8 (zero-extended)
subs r0, r0, r0, lsl #24 // NZCV=1001 from 0x80 - INT_MIN (0x80000000) overflowing to negative result 0x80000080
lsrs r0, #1 // R0 = 0x40000040, NZCV=0001 (checked with QEMU + LLDB)
在Thumb-2机器码中,有2个指令是16位的。我还没有想到一种方法可以使所有3个指令都具有16位的Thumb编码,无论是使用这种策略还是其他策略。
0: 2080 movs r0, #0x80
2: ebb0 6000 subs.w r0, r0, r0, lsl #24
6: 0840 lsrs r0, r0, #0x1
使用借位/进位输入到sbc/adc?不,没有帮助。
0 - (1<<31) - 1
等于
0x7fffffff
,就像
0 - INT_MIN - 1
一样。将其作为一个操作来执行就像
0 - (INT_MIN + 1)
,从0中减去一个负数并得到一个正数而不溢出。使用ARM指令,例如
rscs r0, #0
,可以清除C和V标志。
0 - 0x7fffffff - 1
产生 0x80000000
= INT_MIN,所以这甚至不是有符号溢出。从一个正数中减去也不会有,但是从一个负数中减去会有。但是负数比正数更高,所以减法不会有借位,所以 -3 - 0x7fffffff - 1
结果为 C=0。如果我们从小于 -1
的负数开始,我们甚至不需要 sbc
,所以这并没有帮助。
0 + 0x80000000 + 1
使用 adc
也没有帮助,这是 INT_MIN + 1
,它没有有符号溢出并且是负数。任何涉及加法的有符号溢出都会有进位或者负结果,即使使用 adc
。
所以在这里,adc
、sbc
或者仅限于 ARM 模式的 rsc
都没有帮助。