北航计算机学院-计算机组成原理课程设计-2021秋
PreProject-MIPS
MIPS 汇编程序解析
本系列所有博客,知识讲解、习题以及答案均由北航计算机学院计算机组成原理课程组创作,解析部分由笔者创作,如有侵权联系删除。
从本节开始,课程组给出的教程中增添了很多视频讲解。为了避免侵权,本系列博客将不会搬运课程组的视频讲解,而对于文字讲解也会相应地加以调整,重点在于根据笔者自己的理解给出习题的解析。因此带来的讲解不到位敬请见谅。
第一个汇编程序
现在,我们通过几个简单的汇编程序,理解汇编语言的形式和各个指令的用途。同学们可以把这些代码复制到自己的 MARS 编辑器中,并在自己的电脑上运行一遍,注意 MARS 调试功能的使用,使得同学们可以更清楚地看到寄存器中数值的变化,明白各个指令的意义。有关循环的一些内容,在后文中会有详细的解释。
下面以一个经典的题目为例,编写我们的第一个汇编程序。
题目内容:输出 ”Hello World“
文本代码:
.data
str: .asciiz "Hello World"
.text
la $a0, str
li $v0, 4
syscall
li $v0, 10
syscall
这是最简单的一个 MIPS 汇编程序,同学们可以把这段代码复制到 MARS 中,在运行之前确保(Mars->Settings->Memory Configuration)为 Compact,Data at Address 0。这个选项,代表数据(.data)段的地址是从 0x0 开始,程序(.text)段的地址是从 0x3000 开始。
下面我们一行一行地分析这段代码。
第 1 行 .data,代表变量的声明和分配从这里开始
第 2 行 str: .asciiz “Hello World”,代表分配了一定的内存空间,用来存储这个字符串,字符串最后有一个 ’\0’,str 是这个字符串的标签 (label)
第 4 行的 .text 代表程序从这里开始
第 5 行的 la,是一个扩展指令,在按 F3 快捷键之后进入 Execute 页面,可以看到这一个指令被转换成了 addi $4, $0, 0;同理,第 6 行和第 8 行的 li,也是一个扩展指令,这一个指令被转换成了 addiu $2, $0, 4 和 addiu $2, $0, 10
第 7 行和第 9 行的 syscall,会根据当前的 $v0 寄存器的值,进行相应的操作,比如执行第 7 行 syscall 的时候,$v0=4,此时就会输出 $a0 寄存器指向的字符串,执行第 10 行 syscall 的时候,$v0=10,此时程序结束运行。有关 syscall 的说明,可以按快捷键 F1,在 MIPS -> Syscalls 页面中查到相应的用法。
这个 MIPS 汇编程序在运行的时候,先把 str 的地址 (0x0) 赋值给 $a0,然后令 $v0=4,执行syscall,输出 $a0 寄存器指向的字符串 ”Hello World”,直到遇到 ’\0’ 为止,最后令 $v0=10,执行 syscall,结束程序。
汇编语言与我们之前所学习的 C 语言还是有很大的区别,由于汇编语言的主体代码中并没有变量名、函数名、各种运算符号和语句块。我们仅能通过有限的 32 个寄存器以及众多的汇编指令和标签来实现我们之前所学的 C 语言所能够实现的功能。实际上 C 语言代码在经过编译后也会转换成汇编程序代码再进一步翻译成机器码,有兴趣的同学可以自行搜索相关资料进行学习,将来在操作系统和编译这两门课程中你们还将继续与这些汇编程序打交道。
如果认真阅读了上面的代码,想必已经体会到了汇编指令在可读性方面的巨大缺陷,所以请务必为自己编写的汇编程序代码加上注释,防止代码写完之后自己都看不懂。
一个简单的循环例子
下面以一个简单题目为例,说明在 MIPS 中如何使用循环。
题目内容:输入一个数 nn,输出 1+2+3+…+n1+2+3+…+n(保证 nn 和输出结果都在 int 的范围之内)
文本代码:
.text
li $v0,5
syscall # 输入一个整数,输入的数存到 $v0 中
move $s0, $v0 # 赋值,$s0 = $v0
li $s1, 0 # $s1 用于存储累加的值,$s1 = 0
li $t0, 1 # $t0 是循环变量
loop:
bgt $t0, $s0, loop_end # 这里用了一个扩展指令 bgt,当 $t0 > $s0 的时候跳转到 loop_end
add $s1, $s1, $t0 # $s1 = $s1 + $t0
addi $t0, $t0, 1 # $t0 = $t0 + 1
j loop # 无条件跳转到 loop 标签
loop_end:
move $a0, $s1 # 赋值,$a0 = $s1
li $v0, 1 # $v0 = 1,在 syscall 中会输出 $a0 的值
syscall
li $v0,10 # $v0 = 10
syscall # 结束程序
在运行之前确保(Mars -> Settings -> Delayed branching)前面不要打钩,即不考虑延迟槽,遇到跳转指令立刻跳转,不执行跳转指令之后的指令。(在 MIPS 教程和 P2 部分,如果不对延迟槽加以说明,我们都不用考虑延迟槽)
相比于”第一个汇编程序“小节中的输出 ”Hello world“ 的程序,这里多了 syscall 输入和输出整数的用法,以及 move、bgt 扩展指令,在 Execute 页面中对照 Basic 列和 Source 列的不同,就可以明白那条扩展指令是什么意思。利用这些扩展指令,可以简化我们编写汇编代码的工作量,并使得我们的汇编代码更加清晰。

在 MIPS 中使用数组
下面以一个简单题目为例,说明在 MIPS 中如何使用数组。
题目内容:输入一个整数 nn(2 \leq n \leq 102≤n≤10),接下来 nn 行,每行一个整数(在 int 范围内),在输出的时候先输出 The numbers are:,然后把这 nn 个整数按照输入顺序输出,两个数字中间一个空格,行末可以有空格。
样例输入:
4
2
3
1
4
样例输出:
The numbers are: 2 3 1 4
文本代码:
.data
array: .space 40 # 存储这些数需要用到数组,数组需要使用 10 * 4 = 40 字节
# 一个 int 整数需要占用 4 个字节,需要存储 10 个 int 整数
# 因此,array[0] 的地址为 0x00,array[1] 的地址为 0x04
# array[2] 的地址为 0x08,以此类推。
str: .asciiz "The numbers are:\n"
space: .asciiz " "
.text
li $v0,5
syscall # 输入一个整数
move $s0, $v0 # $s0 is n
li $t0, 0 # $t0 循环变量
loop_in:
beq $t0, $s0, loop_in_end # $t0 == $s0 的时候跳出循环
li $v0, 5
syscall # 输入一个整数
sll $t1, $t0, 2 # $t1 = $t0 << 2,即 $t1 = $t0 * 4
sw $v0, array($t1) # 把输入的数存入地址为 array + $t1 的内存中
addi $t0, $t0, 1 # $t0 = $t0 + 1
j loop_in # 跳转到 loop_in
loop_in_end:
la $a0, str
li $v0, 4
syscall # 输出提示信息
li $t0, 0
loop_out:
beq $t0, $s0, loop_out_end
sll $t1, $t0, 2 # $t1 = $t0 << 2,即 $t1 = $t0 * 4
lw $a0, array($t1) # 把内存中地址为 array + $t1 的数取出到 $a0 中
li $v0, 1
syscall

本文详细解析了MIPS汇编语言的基础知识,包括如何输出字符串、实现循环、操作数组以及使用系统调用。通过实例展示了简单的汇编程序,如输出HelloWorld,并逐步分析了代码中的每个指令和伪指令的作用。此外,还介绍了如何在MIPS中处理循环和数组,包括输入整数、输出整数、使用数组存储和输出多个整数。最后,文章通过一个求斐波那契数列的例子,演示了如何使用循环和数组来计算并输出一系列数值,以及如何通过系统调用进行字符串和整数的输出。整个过程突显了MIPS汇编语言的指令使用和程序设计思想。
最低0.47元/天 解锁文章
9745

被折叠的 条评论
为什么被折叠?



