Skip to content

逻辑运算:AND、ORR、EOR 与 MVN

逻辑运算:AND、ORR、EOR 与 MVN封面

前三课里,我们一直把寄存器当成装数字的盒子:加减乘除看数值大小,LDR/STR 看地址高低。这一课换个视角——把寄存器拉近到电路层面,你会发现每个寄存器其实是 32 个并排的小开关,每个开关只有开(1)和关(0)两种状态。

逻辑运算就是直接拨弄这些开关的指令:AND、ORR、EOR,以及一个独来独往的 MVN。它们不关心「42 比 26 大多少」这种问题,只关心每一位上是 0 还是 1。

本系列基于 ARMv7(32 位版本),实验照旧可以在 CPUlator(https://cpulator.01xz.net/,选 ARM + ARMv7)里单步跟进。这一课强烈建议在手边备一个「十六进制转二进制」的计算器——很多结果在十六进制下平平无奇,转成二进制才会拍案叫绝。

1. 先重温真值表

大学数字电路课的老朋友来了。逻辑运算一次只看一位:输入 A、输入 B,输出一个结果位。四条指令对应四种规则——AND:两个输入都是 1,结果才是 1;OR:任意一个是 1,结果就是 1;XOR(异或):恰好一个是 1 才是 1,两个 1 或两个 0 都得 0;NOT:没有第二个输入,0 翻成 1,1 翻成 0。

图 1 · 四种逻辑运算的真值表:AND 求全,OR 求任一,XOR 求恰好一个,NOT 全部翻面

图 1 · 四种逻辑运算的真值表:AND 求全,OR 求任一,XOR 求恰好一个,NOT 全部翻面

把这张表记牢,这一课后面所有的「为什么是这个结果」,都能回到这里找到答案。

2. AND:两位都为 1 才放行

语法骨架和 ADD 一模一样:AND Rd, Rn, operand2——目标寄存器写在最前,后面两个输入按位相与,结果存进 Rd:

asm
    mov r0, #0x42      @ R0 = 0100 0010
    and r1, r0, #0x26  @ R1 = R0 AND 0x26

0x26 的二进制是 0010 0110。逐位对齐之后,只有「上下都是 1」的位才能活下来:

图 2 · 0x42 AND 0x26 = 0x02:8 位里只有一对开关同时为开

图 2 · 0x42 AND 0x26 = 0x02:8 位里只有一对开关同时为开

再看一个反直觉的例子:拿 0x42 去 AND 0x15(0001 0101),结果是 0x00。两个数都不小,但它们的 1 位完美错开、一位都不重叠——逻辑运算看的是位的位置,不是数值的大小。

注: AND 的经典用途是「掩码」:想保留哪几位就和 1 相与,想清掉哪几位就和 0 相与。比如 x AND 0x0F 只保留低 4 位,其余全部清零。

3. ORR:ARM 的「或」要双写 R

写过 x86 的同学这里要踩一脚刹车:ARM 的或指令不叫 OR,叫 ORR——两个 R,少写一个汇编器直接报错。至于为什么是双 R,没人说得清,背下来就好。

规则是「任一为 1 即得 1」,语法和 AND 完全对称:

asm
    mov r0, #0x42      @ R0 = 0100 0010
    orr r1, r0, #0x26  @ R1 = R0 ORR 0x26

还是那两个数,结果变成了 0x66(0110 0110)——只要上下有一个 1,那一位就被点亮:

图 3 · 同一对输入:ORR 得 0x66(任一为 1 即点亮),EOR 得 0x64(两个 1 的位被拒绝)

图 3 · 同一对输入:ORR 得 0x66(任一为 1 即点亮),EOR 得 0x64(两个 1 的位被拒绝)

注意: 写 or 能通过的是 x86。ARM 里只有 orr 是合法助记符——原视频作者演示时都忘了双写 R,编译当场失败,这个坑的新手中招率极高。

4. EOR:恰好一个 1 才算数

异或在 x86 里叫 XOR,在 ARM 里叫 EOR(Exclusive OR)。它的规则最挑剔:两个输入必须「一真一假」才输出 1。两个 1?不行,太齐了;两个 0?也不行,什么都没有。

asm
    mov r0, #0x42      @ R0 = 0100 0010
    eor r1, r0, #0x26  @ R1 = R0 EOR 0x26

结果是 0x64(0110 0100)。回头看图 3:ORR 和 EOR 的唯一差别,就在那对「上下都是 1」的位上——ORR 放行,EOR 拒绝。

EOR 有两个非常实用的小性质:任何位和 1 异或会翻转、和 0 异或保持不变,想翻转哪几位就 EOR 一个对应位为 1 的值;另外 x EOR x 恒等于 0,所以它常被用来快速清零,或判断两个值是否相等。

5. MVN:独来独往的取反

最后一位选手画风突变:MVN(Move NOT)只有一个源操作数。它不需要第二个输入,干的事只有一件——把源操作数的全部 32 位,0 翻成 1、1 翻成 0,然后搬进目标寄存器:

asm
    mov r0, #0x42      @ R0 = 0x00000042
    mvn r1, r0         @ R1 = R0 的每一位取反

单步执行后,R1 里是 0xFFFFFFBD。第一次看到这个结果的人大多会愣一下:我只动了低 8 位,高位怎么冒出一串 F?

答案就在「32 位」这三个字里。R0 的完整样子是 0x00000042——前面那 24 个 0 也是位,也参与翻转,全部变成 1,十六进制里就是六个 F。取反从来不分高位低位,32 个开关一视同仁。

图 4 · MVN 翻转全部 32 位:高 24 位的 0 全变成 1,十六进制下就是一串 F

图 4 · MVN 翻转全部 32 位:高 24 位的 0 全变成 1,十六进制下就是一串 F

注: 别把 MVN 当成 MOV 的笔误。MOV 是原样搬运,MVN 是搬的时候顺手把每个位翻个面。

6. 寄存器还是立即数?都行

和 ADD/SUB 一样,AND、ORR、EOR 的第二源操作数(operand2)两种写法都合法:

asm
    and r1, r0, #0x26  @ operand2 是立即数
    and r1, r0, r2     @ operand2 是寄存器

MVN 只有一个源操作数,同样两种形式都支持。倒是第一课的老规矩别忘了:立即数前面必须带 #。

小结

这一课我们放下了「数值」,拿起了「位」:

  • 真值表:AND 双全才得 1,OR 任一即 1,XOR 恰好一个,NOT 全部翻面
  • AND Rd, Rn, operand2:按位与,经典用途是掩码——和 1 保留,和 0 清除
  • ORR:ARM 的「或」要双写 R;0x42 ORR 0x26 = 0x66
  • EOR:恰好一个 1 才得 1;0x42 EOR 0x26 = 0x64;x EOR x 恒为 0
  • MVN:单操作数取反,32 位全翻——0x00000042 变成 0xFFFFFFBD,那串 F 就是这么来的

能读写内存、能算数、还能逐位拨开关之后,程序还差最后一块拼图:根据条件走不同的路。下一课我们将请出 CMP 和分支指令,让 CPSR 那几盏灯真正派上用场。下一课见。

本章自测