解释一段汇编 MIPS 代码?

Explaining a section of assembly MIPS code?

我们最近在 class 中编写了这段代码。我浏览并添加了注释,向自己解释它是如何工作的。我只是不明白循环如何计算字符串中 chars/spaces 的数量。有人可以翻译给我吗?

#Comment

.data

Str:.asciiz "hello worlds"

prompt:.asciiz "The given string is: "

ans:.asciiz "\n The number of chr and spaces is = "

.text



.globl main

main:

la $a0, prompt #load string prompt into register a0

li $v0, 4 #print string commmand(prints the prompt)

syscall #executes the command


la $a0, Str #loads Str into a0 

li $v0, 4 #print string commnad

syscall #executes the command


li $t0, 0 #set t0 to 0

la $t1, Str #load Str into register t1


Loop1: #begins the loop SECTION I DONT UNDERSTAND

lb $t2, 0($t1) #loads byte into t2?

beqz $t2, Tim #if t2 == to zero go to Tim ?

addi$t0,$t0,1 #incremet t0 by 1 ?

addi$t1,$t1,1 #initialize t1 to 1 ?

j Loop1

syscall #executes the loop


Tim:
la$a0,ans #load ans into a0

li$v0,4 #print ans string

syscall #executes command


move$a0,$t0 #move t0 to a0

li$v0,1 #print int

syscall #execute command

此代码计算以 null 结尾的 C 风格字符串中的字节数。

让我们把它翻译成 C 语言看看它在做什么,这里我们使用 if-goto 风格模仿程序集的控制流。

Loop1:
  int ch = *ptr;
  if ( ch == 0 ) goto Tim;      // ch is $t2
  counter++;                    // counter is $t0
  ptr++;                        // ptr is $t1
  goto Loop1;

如果我们将其转化为结构化编程,我们将得到:

while ( *ptr != '[=11=]' ) { ptr++; counter++; }

有道理吗?


顺便说一句,循环之后的 syscall 没有任何正常的系统调用设置,所以看起来很可疑;然而,需要明确的是,它是死代码——位于无条件跳转和下一个标签之间的代码(几乎)肯定无法被程序的任何控制流访问(我们称之为死代码;它应该被删除——并且评论有误)。


使用 $t0——调用约定易失性(临时)寄存器——在打印式系统调用中保持计数器的活动也是可疑的。有时这会起作用,但在其他环境中则不起作用。使用像 $s0 这样的被调用者保存 (preserved/non-volatile) 寄存器或内存位置而不是依赖 $t0 保持不变会更合适。

让我们从头开始(在循环之前跳过 2 个系统调用):

分析的字符串 hello worlds 位于地址 Str

t0 将包含 hello worlds 中的字符数。这就是我们需要查找和打印的内容。它由零初始化:
li $t0, 0

t1包含当前处理的字符地址。它由Str初始化,它是hello worlds的地址,同时也是它第一个字符h:
的地址 la $t1, Str

循环开始:

迭代 1:

当前处理的字符加载到t2:
lb $t2, 0($t1)
回想一下 lb reg, offset(addr) 意味着将地址 addr + offset 处的 1 字节值加载到寄存器 reg 中。在我们的例子中,它意味着将地址 t1 + 0 处的 1 字节值加载到 t2 中。 t1 + 0 是当前字符的地址,即 h。因此 h 被加载到 t2

beqz $t2, Tim 表示如果 t2 为零,则跳转到标签 Tim。但 t2 的值为 h,因此它不为零。此检查的目的是检测表示字符串结尾的空终止符。一旦我们检测到空终止符,我们就完成了,循环必须结束

addi $t0, $t0, 1 - 我们增加我们的计数器(到目前为止我们已经处理的字符数)。 t0 变成 1

addi $t1, $t1, 1 - 我们递增当前处理的字符的地址。换句话说,我们继续处理字符串的下一个字符(我们将其递增 1,因为字符大小为 1 个字节)。现在 t1 有第二个字符的地址,即 e

j Loop1 - 我们开始新的迭代

迭代 2:

lb $t2, 0($t1) - e 加载到 t2
beqz $t2, Tim - e 不为零,所以我们不跳转到 Tim 并继续循环
addi $t0, $t0, 1 - t0 变为 2
addi $t1, $t1, 1 - t1 包含第三个字符的地址,即 l
j Loop1 - 开始新的迭代

依此类推,直到 beqz $t2, Tim 检查 t2 包含零,在这种情况下程序停止循环并跳转到 Tim