标签本身不存储在任何地方。它只是汇编器/链接器的符号地址。跳转j again
指令操作码确实存储了实际的结果地址,就像一个数字一样。
链接器会将所有目标文件粘合在一起,合并目标文件中的所有符号并填充正确的相对地址 + 为 OS 加载程序创建重定位表,生成可执行文件。
操作系统在加载可执行文件时还将加载重定位表,根据实际地址修改/填充使用绝对地址的指令,二进制文件被加载的位置,然后丢弃重定位表,并执行代码。
因此,标签只是程序员的“源代码”,是特定固定内存地址的别名,以使程序员免于计算实际指令操作码大小和计算头部或内存变量地址中的跳转偏移量。
在编译一些汇编源代码时,您可能希望从汇编器中检查“列表文件”(通常是/l
开关),以查看生成的实际机器代码字节(标签没有)。
您在编译时的“任务”代码0x00400000
如下所示(我将它们设置add
为 t1=t1+t1 以在其中包含任何内容):
Address Code Basic Source
0x00400000 0x01294820 add $9,$9,$9 4 add $t1,$t1,$t1
0x00400004 0x01294820 add $9,$9,$9 5 add $t1,$t1,$t1
0x00400008 0x11090004 beq $8,$9,0x00000004 6 beq $t0, $t1, next
0x0040000c 0x1509fffc bne $8,$9,0xfffffffc 7 bne $t0, $t1, again
0x00400010 0x01294820 add $9,$9,$9 8 add $t1,$t1,$t1
0x00400014 0x01294820 add $9,$9,$9 9 add $t1,$t1,$t1
0x00400018 0x01294820 add $9,$9,$9 10 add $t1,$t1,$t1
0x0040001c 0x08100000 j 0x00400000 11 next: j again
如您所见,每条实际指令都会产生 32 位值,有时称为“操作码”(操作码),该值在“代码”列中可见。“地址”列表示,该值存储在内存中的位置,何时加载可执行文件并准备执行。“基本”列显示了从操作码反汇编回来的指令,最后一个位置是“源”列。
现在看看条件跳转如何将相对跳转值编码为 16 位(beq $8, $9
操作码是0x1109
,而其他 16 位0x0004
是 16 位符号扩展值“要跳转多少”)。该值表示远离“当前位置”的指令数,其中当前是后续指令的地址,即。
0x0040000c + 0x0004 * 4 = 0x0040001c = target address
*4,因为在 MIPS 上,每条指令的长度正好是 4 个字节,并且内存寻址是按字节工作的,而不是按指令工作的。
next 也是一样bne
,opcode 本身是0x1509
,offset 是0xfffc
,也就是-4。=>
0x00400010 + (-4) * 4 = 0x00400000
绝对跳转使用不同的编码,它是 6 位操作码0b000010xx
(xx 是与操作码一起存储在第一个字节中的两位地址j
,在本例中它们为零),然后是 26b 地址除以四0x0100000
,因为每条指令都必须从对齐地址开始,所以编码两个最低有效位是浪费的,它们总是00
。0x100000 * 4 = 0x00400000
...我懒得检查它在 MIPS 上的工作方式,但我认为j
定义位 2-27、0-1 是零,而 28-31pc
可能是从当前复制的?使 CPU 能够在整个 4GiB 地址范围内工作,但可能有一些特殊的方法可以在不同的“银行”(高 4 位pc
)之间跳转。我不确定,我从来没有为 MIPS 编写过代码,所以我没有阅读CPU规格。
无论如何,如果您说again:
is at 0x10010020
,所有这些都可以重新计算以遵循准备好执行的生产功能代码0x10010020
(虽然这j
很棘手,但您必须确定总地址是如何组成的,如果高4位被复制或什么)。
BTW,真正的MIPS CPU会延迟分支(即总是执行分支跳转后的下一条指令,同时评估条件,跳转发生在下一条指令之后),我认为pc
用于计算目标地址的也是1指令“稍后”一个,因此真正 MIPS 的正确代码将beq
在第二个之前具有该指令add
,但相对偏移量仍为0x0004
. :) 简单吧?如果这对您没有意义,请检查 MARS 设置(延迟分支的模拟默认关闭,以免混淆学生),并搜索谷歌以获得更好的解释。漂亮的小有趣 CPU,就是 MIPS。:)