4 回答
分支允许条件。但是允许条件在指令中占用更多位。因此,一个分支的地址只有 2^16 位,并且只允许您向后分支 2^15 - 1 条指令或向前分支 2^15 条指令。
跳转是无条件的,通过省略条件保存的位可用于地址。跳转允许 26 位地址,因此可以在代码中跳转比分支更远的地方。以没有条件为代价。
分支 ( b
) 使用 PC 相对位移,而跳转 ( j
) 使用绝对地址。这种区别对于与位置无关的代码很重要。此外,只有跳转可用于间接控制传输(jr
,使用寄存器值)。
如前所述,分支的位数更少,范围更短并且是相对的。Jump 有更多的位并且是绝对的。
举这个例子
b l0
nop
beq $0,$1,l1
nop
j l2
nop
l0: .word 0,0
l1: .word 0,0
l2: .word 0,0
你得到这个
00000000 <l0-0x1c>:
0: 10000006 b 1c <l0>
4: 00000000 nop
8: 10010006 beq zero,at,24 <l1>
c: 00000000 nop
10: 0800000b j 2c <l2>
14: 00000000 nop
18: 00000000 nop
0000001c <l0>:
...
00000024 <l1>:
...
0000002c <l2>:
...
现在其他答案可能没有提到的是,无条件分支至少由 gnu 汇编程序编码为具有相同寄存器的分支(如果相等)。mips 中没有无条件分支,据我所知,如果相等则有分支,如果不相等则有分支。
您在上面看到跳转使用 0xB 是字地址,0xB*4 = 0x2C 是目标地址,其中条件使用相对寻址 pc+(signed_offset*4) 其中 pc=instruction_address+4; 或者取指令地址 + 4 + (signed_offset*4) 得到目的地址。
将别名 b 用于分支而不是 j 用于跳转将创建与位置无关的代码。如果你四处走动,跳转不会,必须重新链接,因为近距离跳转可能更好地使用分支而不是跳转,即使它是别名。如果你是一个纯粹主义者,那么你可以使用真正的指令 beq $0,$0,label 或选择任何寄存器 beq $4,$4,label。寄存器 0 特殊且快速可能是更好的选择。
在 MIPS 中,跳转和无条件分支是不一样的。
分支和跳转指令都将数据写入程序计数器寄存器,以便在下一个取指周期时,将取指不同的指令,而不是程序存储器中的下一条指令。从这个意义上说,它们执行相同类型的操作。
它们的不同之处在于分支是有条件的,如果满足某个条件,它们只会更改要执行的下一条指令。这可以通过if
语句中执行代码的差异或调用函数来说明。
if (a == 0) {
a = 1
}
setAtoOne()
该if
语句仅跳转到要设置的指令a = 1
if a == 0
。无论如何,该函数将跳转到该指令。
在这种情况下,我们讨论的是条件始终为真的分支。这只是另一种写作方式
beq $zero, $zero, (int)offset
$zero 总是等于 $zero,所以它总是分支到指定的偏移量。就像这样的 if 语句
if (true) { a = 1 }
分支和跳转指令之间还有另一个区别。跳转指令指定 PC 将设置到的绝对地址,而分支指令偏移程序计数器中的地址。
PC = 32-bit address # Jump
PC += 16-bits lower
实际上,这并不完全正确。我们用绝对地址和偏移量编写程序集,但在跳转和分支中,它都被编译为偏移量。这就是为什么你不能跳转或跳转到内存中的任何地方,期望使用跳转到寄存器jr
指令。这是因为 MIPS 的基本设计,固定长度的单字指令。
所有 MIPS 指令都是 1 个字(即 4 个字节/32 位)长。它们包含一个 6 位指令 ID(称为操作码)以及执行指令所需的其他信息。这可能是寄存器的 id 或“立即”值,基本上是指令中编码的整数。
MIPS 中内存中的每个字节在0x00000000
-之间都有一个地址0xFFFFFFFF
。要获取其中一个字节,我们需要指定地址。如果幸运地将地址存储在寄存器中,我们将只jr
使用已存储在寄存器中的地址。然而,我们不是。
这变得有问题,我们的指令只有 32 位,我们需要所有这些位来指定该范围内的地址。我们还必须放弃 6 位供处理器用来识别指令。现在我们剩下 26 位。
更糟糕的是,当我们进行分支时,我们需要 10 个额外的位来指定我们正在比较的两个寄存器以适应我们的条件。解决方案是使用偏移量。
假设我们在 address0x12345678
并且我们正在执行无条件跳转到 memory 中的下一个地址j 0x1234567c
。这是汇编代码,我将展示如何将其转换为机器代码并执行。
首先我们作弊一点。我们知道指令是一个字(4 个字节),并且在 MIPS 中指定它们必须在字边界内。这意味着所有指令都具有相隔 4 个字节的地址,这意味着它们在二进制表示中始终以 00 结尾。太好了,我们可以剃掉那两个无意义的部分。我们还剃掉了前 6 个,但别担心,我们稍后会取回它们。
jump 0001 0010 0011 0100 0101 0110 0111 1100
jump 0001 0010 0011 0100 0101 0110 0111 1100
0000 1000 1000 1101 0001 0101 1001 1111 #in machine code # jump op = 0000 10
当我们执行这个时,我们采取
00 1000 1101 0001 0101 1001 1111
0000 0000 1000 1101 0001 0101 1001 1111 # extend >> 6
0000 0010 0011 0100 0101 0110 0111 1100 # << 2
然后我们和PC(我们正在执行的地方)和0xf0000000
0001 0010 0011 0100 0101 0110 0111 1000
1111 0000 0000 0000 0000 0000 0000 0000
AND
0001 0000 0000 0000 0000 0000 0000 0000
我们知道将结果与我们的指令整数相或
0001 0000 0000 0000 0000 0000 0000 0000
0000 0010 0011 0100 0101 0110 0111 1100
OR
0001 0010 0011 0100 0101 0110 0111 1100
这是0x1234567c
十六进制的,我们想去的地方,现在我们跳到那里。这就是为什么你不能从当前指令跳转到超过 256MB(2^28 位)的地方(除非你跳转到寄存器的值jr
)
相同的基本思想适用于分支,除了现在您还比较了 2 个寄存器(需要 10 位),因此您只有 16 位可用于偏移,因此您不能用分支跳得那么远。
一般来说,这很好,因为我们主要在过程中使用分支来实现循环和执行条件赋值。
这都是 MIPS 架构设计的结果。完全有可能有指令,其中分支和跳转之间的唯一区别是条件方面,而“无条件”分支的行为与无条件跳转相同。