BUAA计组p2_Mips_复习tips
p2 MARS
汇编程序回顾
重点:运行之前确保(Mars-Settings-Memory Configuration)为Compact,Data at Address 0
由于汇编语言的主体代码中并没有变量名、函数名、各种运算符号和语句块。我们仅能通过有限的32个寄存器以及众多的汇编指令和标签来实现我们之前所学的C语言所能够实现的功能。
-
伪指令
-
.data 定义程序数据段的初始地址
-
.text 定义程序的代码段
-
.space
- name: .space n
- name的地址是由.data段的初始地址加上前面所申请的数据大小计算得出的
- 申请空间时尽量让n为4的倍数
- 以字节为单位申请空间,1byte,一个int占4个字节
-
.word
- name: word 0:n
- 在内存数据段以字为单位连续存储数据,32bit
-
.asciiz
- name: .asciiz:"string"
- 以字节为单位存储字符串,末尾以NULL结尾
- 由于按字节存储,尽量放在字节分配空间之后
-
-
宏的使用
- .macro 实现代码复用,不允许有嵌套宏指令
.macro macro_name(%parameter1, %parameter2, ...) //代码段 .end_macro
- .eqv 增强代码可读性
.eqv EQV_NAME string
-
MIPS汇编程序设计
-
条件语句
slt $t3, $t1, $t2 # if(t1<t2) t3=1; beq $t3, $0, if_1_else # do something j if_1_end if_1_else: # do something else if_1_end:
-
循环语句
for_begin1: slt $t3, $t2, $t1; beq $t3, $0, for_end1 # do something addi $t2, $t2, 1 j for_begin1 for_end1:
矩阵
matrix_multi
- 矩阵读入
- 使用栈读入矩阵,保留三个矩阵的起始位置,计算元素在矩阵中的偏移量
- 矩阵乘法
- 计算偏移量,计算结果,并存入目标地址
- 输出结果
- for循环 + 计算偏移量 + 输出结果
常用汇总
-
.word申请存储空间时,为.word 0:n, n为实际字节数
matrix: .word 0:100 # 10\*10\*1word 即申请出10*10的int型数据二维矩阵
-
将题目中的固定输出赋予label写在.data下
space: .asciiz " "
-
sw, lw 对word的操作
- sw即store word
sw $t3, array($t4) # store contents of $t3 into array[index]
- lw即load word
lw $t3, symbol($t4) # load contents of $t3 into symbol[i]
- 同理,lb和sb为对byte的操作
-
li, la
- la即load address
- li即load immediate
-
常用的syscall命令
读入整数 li $v0, 5 # read an integer into $v0 syscall
输出字符串 la $a0, space # $a0 = address of null-terminated string to print li $v0, 4 syscall
输出整数 move $a0, out # $a0 = integer to print li $v0, 1 syscall
读入字符 li $v0, 12 # $v0 contains character read syscall
结束程序 li $v0, 10 syscall
-
除法:商存在lo中,余数存在hi中; 乘法:低四位存在lo中,高四位存在hi中
递归
首先需要理解递归本身的性质或运行流程,在此基础上进行mips编写
mips代码示例为全排列一题
-
递归流程:判断是否终止递归、进入终止输出并回溯 或 进入包含递归模块的核心部分
- 递归模块重点:递归终止条件、递归进入变量存储、递归回溯
- 递归终止条件
- 即递归终止的情况
- 递归进入变量存储
- 递归中参数的存储
- 进入新一轮递归前的参数
- 每个递归跳转指令的下一指令的地址的存储
- $ra中存储着jal recursion指令的下一指令,即本次递归结束时的下一条指令地址
sw $ra, 0($sp) # store address addi $sp, $sp, -4 sw index, 0($sp) addi $sp, $sp, -4 # store index sw i, 0($sp) addi $sp, $sp, -4 #store i
- $ra中存储着jal recursion指令的下一指令,即本次递归结束时的下一条指令地址
- 递归回溯
- 按照存储顺序的逆序将本递归的参数取回来继续完成本次递归函数
sw $ra, 0($sp) # store address addi $sp, $sp, -4 sw index, 0($sp) addi $sp, $sp, -4 # store index sw i, 0($sp) addi $sp, $sp, -4 #store i
- 递归回溯后的操作:
bne $t4, 8, nxt li $s1, 0 nxt: add $s2, $s1, $s0 sw $s2, fib_array($t4) move $s1, $s0 #前前一个 move $s0, $s2 # $s0 前一个 jr $ra
- 按照存储顺序的逆序将本递归的参数取回来继续完成本次递归函数
tips
- 由于寄存器数量问题,经常会有反复使用同一寄存器的操作,为避免复用了不该改动的寄存器,.eqv从$s7或者$t7开始由大到小使用
- 寄存器名字经常会造成写着写着迷糊的情况,建议勤用.eqv,命名直观最重要,如果缺寄存器则一定要多加注释帮助自己理清思路!
- 做题逻辑
3.1 先理清题目的模块顺序,比如约瑟夫环的题目:先读入n,再循环结构得到所需结果的数组,最后输出模块
3.2 将核心模块的具体流程落实写出来,可以是伪代码的形式,也可以是直接写出C,然后将寄存器与核心变量一一对应
3.3 逐层对照翻译,或者是按照汇编的思路写完,注意初始化、结尾跳转、寄存器使用情况
3.4 最好是一遍写对,写完之后,如果是偏简单的题目加简单注释,稍复杂的务必加详细的注释,然后再去debug
课上测试
-
上机调试好环境之后不要再随意按键盘上的键了,机房电脑重启很烧时间。
-
遇到Bug优先考虑局部修改,而不是全盘推倒重写。一是不一定有或不一定想得到更好的算法,而是重写耗时颇高。
- 此类汇编题目有限制步数要求时,如果TLE了,优先考虑删减掉大部分情况下不必要的步骤(而不是傻乎乎的想不出来别的做法自闭)。
-
翻译C的mips汇编题目时,可以稍微调整判断语句的顺序以更适用于汇编的书写。
-
如果担心数据数量过多,直接压栈操作,不过务必记得存储栈顶地址。
-
程序末尾务必加上结束程序的指令(li $v0, 10 \n syscall),否则如果习惯将跳转执行模块label写在末尾的话,程序可能会死循环/玄学bug,该点可以通过调试发现问题,如果是边操作边输出,此类情况输出应正确。