数据的声明与分配

数据储存空间,指的是内存中的空间

寄存器虽然也可以存储数据,但更重要的功能是临时存储以供计算。

控制流

if-else 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text
li $t1, 200
li $t2, 400

slt $t3, $t2, $t1
beq $t3, $zero, if_1_else
nop
la $a0, s1
li $v0, 4
syscall
j if_1_end
nop

if_1_else:
la $a0, s2
li $v0, 4
syscall

if_1_end:

for 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.data
s1: .asciiz " "
.text
li $t1, 100 # use $t1 as n
li $t2, 0 # use $t2 as i
for_1_begin:
slt $t3, $t2, $t1
beq $t3, $zero, for_1_end
nop

##################
# statements #
##################
move $a0, $t2
li $v0, 1
syscall

la $a0, s1
li $v0, 4
syscall

addi $t2, $t2, 1 # i++
j for_1_begin
nop

for_1_end:

输入输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
.data
array: .space 40
message_input_n: .asciiz "input n:\n"
message_input_array: .asciiz "input array:\n"
space: .asciiz " "
message_output_array: .asciiz "output array:\n"

.text

input:
la $a0, message_input_n
li $v0, 4
syscall

li $v0, 5
syscall
move $t0, $v0

li $t1, 0 # t1 as i
for_1_begin:
slt $t2, $t1, $t0
beq $t2, $zero, for_1_end
nop

##################
la $t2, array
li $t3, 4
mult $t3, $t1 # 将$t3和$t1相乘
mflo $t3 # 将结果取回$t3
addu $t2, $t2, $t3

la $a0, message_input_array
li $v0, 4
syscall

li $v0, 5 # 读取一个整数
syscall
sw $v0, 0($t2) # 将整数存在该地址中
##################

addi $t1, $t1, 1 # i++
j for_1_begin
nop

for_1_end:
move $v0, $t0 # 将元素的个数保存,防止被覆盖
#jr $ra # PC值的跳转
nop
move $a0, $v0
output:
move $t0, $a0

la $a0, message_output_array
li $v0, 4
syscall

li $t1, 0
for_2_begin:
slt $t2, $t1, $t0
beq $t2, $zero, for_2_end
nop

##################
la $t2, array
li $t3, 4
mult $t3, $t1 # 将$t3和$t1相乘
mflo $t3 # 将结果取回$t3
addu $t2, $t2, $t3 # 算出数的地址

lw $a0, 0($t2)
li $v0, 1
syscall

la $a0, space
li $v0, 4
syscall
##################

addi $t1, $t1, 1 # i++
j for_2_begin
nop

for_2_end:
#jr $ra # PC值的跳转
nop

子过程调用

PC需要进行跳转

jal跳入子过程,jr返回父过程

栈的使用

  • 过程自身需满足栈的结构
  • 过程调用子过程时需满足展的结构
  • 子过程执行前后需要移动栈指针$sp(分配与释放栈)

image

具体使用

  • 计算好栈帧大小
  • 栈指针始终指向栈顶
  • 过程开始时分配栈空间(addiu $sp, $sp, -32)
  • 过程结束时回收栈空间(addiu $sp, $sp, 32)
  • 以栈指针为基址进行栈的存取(sw $to, 24($sp))

递归程序解析

C语言实现阶乘

image

汇编实现阶乘

image

factorial部分。最开始的三句,其作用等价于我们在C语言factorial函数中的第一句,即递归终止条件。如果此时$a0的值为1,那么会将值1写在$v0寄存器里。意味着1的阶乘值为1。如果$a0的值不为1,我们跳转到work部分。这部分主要实现的是递归调用。第一句,我们将$t0的值设置为$a0,即当前n的值。接下来我们要将当前这个函数中用到的值存入栈中。有用的值有两个,一个是当前n的值,因为我们要利用它进行计算。另一个是当前的$ra寄存器的值,因为此时的$ra的值可能在后面被jal指令再次覆盖,以至于无法跳转到正确的位置。这里需要注意下栈的增长方向,应该是从高到低。所以我们存入一个数之后要将当前$sp的值减去4,一遍下一个数的存放。接着我们要设置传递给下一次函数的参数。具体来说就是n-1的值。我们将其存入$a0中。做好所有这些准备工作,我们就可以进行递归了,通过jal指令再次调用factorial函数。在调用结束后,因为我们巧妙的设计,下一层函数结束后将通过jr指令跳转这里,并接着执行下一条的内容。之前我们将$t0$ra的值存入到了栈中,相应的,我们这里需要将这些值重新写入。并且恢复栈指针的位置。之后我们计算n与下一层函数返回值的乘积,并且当做新的返回值存入$v0之中。最后我们通过jr指令,返回到上一次调用它的地方。

对比下C语言的实现和MIPS汇编的实现。两者的差距主要在于两个部分。第一,C语言中,函数内的临时变量是不需要程序员来通过栈维护的。我们可以理解为,每一层的变量相互之间都是独立的。但是在mips汇编中,我们需要维护这些变量。第二,我们需要维护PC值的变化。C语言中函数结束会自动返回到调用它的位置。但是在mips汇编中,我们需要利用jaljr来实现这个功能。