解释一段汇编 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
我们最近在 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