上一节:https://lixu.cc/major/mips-pre.html
数组
在MIPS代码中,一般会分为两部分,.data部分与.text部分,我们前面写的代码都属于.text部分,但是当我们需要使用数组时,则要使用.data段。
MIPS本身没有专门的“数组”类型,数组其实就是连续内存空间中的一组数据。通过给同一类型的变量分配连续的内存空间实现。在.data段定义数组,实际上是为多个相同类型的数据分配连续内存。
定义数组:
可以以定义数组初始值和定义空间大小两种方法来初始化数组:
.data
array: .word 10,20,30,40,50 # 定义一个大小为5的整型数组
array2: .space 20 # 20字节 = 5个int(4字节int)其中array和array2为设置的数组名字。
加载数组地址:
使用la指令,将某个变量(标签)的地址加载到寄存器中。这是一种伪指令。
la $t0, label其中,label是数据段中定义的符号名(数组、变量等),la指令把label对应的内存地址存入寄存器$t0。
访问数组:
lw:加载字,从内存地址($t0 + offset)取4字节(一个字),放入寄存器$t1。offset是字节偏移,通常是用索引乘以4后的值。
lw $t1, offset($t0)sw:存储字,用于修改数组元素或变量值。把寄存器$t4的4字节数据存放到内存地址($t3 + offset)。
sw $t4, offset($t3)例如:
.data
array:.space 400
.text
la $t1,array #t1内存的是数组首地址
li $t2,3 #array[0]=3
sw $t2,0($t1)
li $t2,4 #array[1]=4
sw $t2,4($t1)
li $v0,1
lw $a0,0($t1) #输出array[0]
syscall
lw $a0,4($t1) #输出array[1]
syscall遍历数组:
原本的lw $a0, 0($t1)的0不能使用变量传递偏移量。
可以设置一个新的变量,也用来存储地址,每次在数组首地址上加4,然后进行sw或者lw操作时可以设置偏移量为0,变量/数组地址直接用新的变量。example:
.data
array:.space 400
.text
li $t2, 4
la $t1,array #t1内存的是数组首地址
addi $t3,$t1,4
sw $t2,0($t3) # 将array[1]设置为4跳转指令
j指令:无条件跳转到指定标签执行代码。
j labeljr指令: 跳转执行寄存器中存储的地址,通常用来返回调用者。最常见用法是从函数返回时跳转回 jal 保存的 $ra。
jr $register$register通常是$ra($31),当执行 jr $ra,程序跳回到之前 jal 保存的返回地址。
jal指令: 跳转到目标标签执行代码,并将下一条指令的地址(返回地址)存入寄存器 $ra ($ra = $31,return address,返回地址寄存器)。
main:
...
jal my_function # 调用my_function
# 执行返回这里,继续后续指令
my_function:
# 函数体
jr $ra # 跳回调用点jalr指令: 与上一条用法相似,只是跳转地址用寄存器存,而不是标签。也可以修改返回地址寄存器位置。
jalr $t0 # 跳转到$t0中的地址,返回地址存到$ra
jalr $t0, $t1 # 跳转$t0地址,$t1保存返回地址一些常用判断跳转指令:
| 指令 | 作用 | 备注 |
|---|---|---|
beq | 等于跳转 | $rs == $rt 时跳转 |
bne | 不等跳转 | $rs != $rt 时跳转 |
blt | 小于跳转(伪指令) | $rs < $rt 跳转,用slt+bne组合 |
bgt | 大于跳转(伪指令) | $rs > $rt 跳转,用slt+bne组合 |
ble | 小于等于跳转(伪指令) | $rs <= $rt 跳转,用slt+beq组合 |
bge | 大于等于跳转(伪指令) | $rs >= $rt 跳转,用slt+beq组合 |
bltu | 无符号小于跳转(伪指令) | 用sltu替代slt |
bgeu | 无符号大于等于跳转 | 同上 |
bgez | 大于等于零跳转 | $rs >= 0跳转 |
bgtz | 大于零跳转 | $rs > 0跳转 |
blez | 小于等于零跳转 | $rs <= 0跳转 |
bltz | 小于零跳转 | $rs < 0跳转 |
其中后面的几乎都可以使用前两条beq和blt指令加上其它比较大小指令(主要是slt)完成。
(slt指令:返回0或1)
slt $at, $t1, $t0 # $at=1 if $t1 < $t0$sp寄存器
$sp(stack pointer)寄存器用于指向当前栈顶的位置,栈一般用于存储函数调用时的局部变量、保存返回地址、保存调用者保存的寄存器等临时数据。通常,栈是从高地址向低地址生长的,所以 sp 通常会递减以分配空间,递增以释放空间。
基本用法:
- 初始化:操作系统或程序启动时会将
$sp初始化为一个内存高地址(栈顶)。 - 分配空间:调用函数时,在栈上为局部变量和保存寄存器分配空间,通过
subu $sp, $sp, n(n 是字节数)来实现。 - 释放空间:函数调用结束后,恢复栈指针,
addu $sp, $sp, n。 - 保存寄存器:函数调用过程中,如果需要保存
$ra(返回地址)、$s0-$s7(保存寄存器)等,需要先把它们压栈(保存到栈空间),返回时再恢复。
因为这个栈是从高地址向低地址生长,所以扩展栈是把栈寄存器的值减字节数,如果释放空间则是把栈寄存器增加字节数。而使用栈的时候,最靠上的位置应该是数越大越靠近内存高位、远离栈顶。
示例: 假设有一个函数 foo,它的执行需要保存 $ra 和 $s0,并且自己有16字节的局部变量空间:
foo:
# 1. 分配栈空间(保存$ra, $s0 + 局部变量共 24 字节)
addiu $sp, $sp, -24
# 2. 保存 $ra 和 $s0 到栈中
sw $ra, 20($sp) # 栈顶偏移20保存$ra
sw $s0, 16($sp) # 保存$s0
# ... 在这里进行函数体操作,可能需要使用$s0等寄存器
# 3. 恢复保存的寄存器
lw $ra, 20($sp)
lw $s0, 16($sp)
# 4. 恢复栈指针
addiu $sp, $sp, 24
# 5. 返回
jr $ra递归
使用上面的跳转指令和$sp寄存器可以完成递归操作。
比如如下代码,输出f(3):
int f(x){
if(x==1) return 1;
return f(x-1)+1;
}汇编代码为:
li $a1,3
jal label
move $a0,$v0 #输出
li $v0,1
syscall
li $v0,10
syscall
label:
beq $a1,1,ret #对应如果x==1,则xxx
addi $sp,$sp,-8
sw $a1,0($sp)
sw $ra,4($sp) #将返回地址与x的值存储起来
addi $a1,$a1,-1 #x-1
jal label #对应执行递归函数f(x-1)
lw $a1,0($sp)
lw $ra,4($sp)
addi $sp,$sp,8 #取出返回地址与x的值
move $s1,$v0
addi $v0,$s1,1 #对应f(x-1)+1
jr $ra #返回
ret:
li $v0,1 #返回值存储在v0寄存器中
jr $ra 函数
在了解递归后,函数应该很简单。一个函数通常会包含以下过程:
- 保存调用者现场(如果有需要)
- 保存返回地址寄存器
$ra - 保存被调用者需要保存的寄存器(如
$s0-$s7) - 分配栈空间用来存储局部变量
- 函数体
- 恢复保存的寄存器、返回地址
- 恢复栈指针
- 返回调用点(使用
jr $ra)
例如很简单的a+b函数:
# 函数参数:a 在 $a0, b 在 $a1
# 返回值:和放在 $v0
add:
add $v0, $a0, $a1 # 计算 a + b
jr $ra # 跳回调用者
main:
li $a0, 10 # 第一个参数 a=10
li $a1, 20 # 第二个参数 b=20
jal add # 调用 add 函数
# 这里 $v0 保存了返回值 30
.data字段
| 伪指令 | 说明 | 示例 |
|---|---|---|
.word | 定义32位(4字节)的整数 | num: .word 100 |
.byte | 定义8位(1字节)的整数 | bval: .byte 1, 2, 3 |
.half | 定义16位(2字节)的整数 | hval: .half 300, 400 |
.space | 分配指定字节数的未初始化空间 | buffer: .space 64 |
.ascii | 定义不以零结尾的字符串 | str1: .ascii "hello" |
.asciiz | 定义以零结尾的字符串 | str2: .asciiz "Hello World!" |
实验二解答
T1
li $v0,5
syscall
move $t1,$v0
li $v0, 5
syscall
move $t2,$v0
loop:
div $t1,$t2
mfhi $t3
move $t1, $t2
move $t2, $t3
bgt $t2,0,loop
li $v0, 1
move $a0, $t1
syscall
li $v0, 10
syscallT2
li $v0, 5
syscall
move $t1, $v0
li $t2, 0 # 个数
li $t3, 2 # 循环变量
loop:
div $t1, $t3
mfhi $t4 # 余数
addi $t3, $t3, 1
bgt $t4, 0, loop
addi $t3, $t3, -1
li $t4, 2 # 测质数循环变量
li $t6, 1 # flag
loop2:
beq $t3, 2, panduan
div $t3, $t4
mfhi $t5
bne $t5, 0, no
li $t6, 0
j panduan
no:
addi $t4, $t4, 1
mul $t7, $t4, $t4
ble $t7, $t3, loop2
panduan:
bne $t6, 1, nxt
# move $a0, $t3
# li $v0, 1
# syscall
addi $t2,$t2,1
nxt:
addi $t3,$t3,1
ble $t3, $t1, loop
move $a0, $t2
li $v0, 1
syscall
li $v0, 10
syscallT3
.data
array: .space 2100
.text
li $v0, 5
syscall
move $t0, $v0 # t0为n
li $t1, 1
la $t4, array
sw $t1, 0($t4)
li $t1, 2 # 循环变量
loop:
li $t2, 0 # 进位
li $t3, 0 # 第二层循环变量
la $t4, array # 第二层循环地址记录
loop2:
lw $t6, 0($t4) # a[j]
mul $t5, $t1, $t6 # temp
add $t5, $t5, $t2
li $t9, 10
div $t5, $t9
mfhi $t6
sw $t6, 0($t4)
mflo $t2
addi $t3, $t3, 1
addi $t4, $t4, 4
ble $t3, 499, loop2
addi $t1,$t1, 1
ble $t1, $t0, loop
la $t9, array
add $t4, $t9, 2000 # k
loop3:
addi $t4, $t4, -4
lw $t7, 0($t4)
beq $t7, 0, loop3
loop4:
lw $a0, 0($t4)
li $v0, 1
syscall
addi $t4, $t4, -4
ble $t9, $t4, loop4
li $v0, 10
syscallT4
.data
buf: .space 4008
.text
li $v0, 5
syscall
move $t0, $v0 # n
li $v0, 5
syscall
move $t1, $v0 # x
li $v0, 5
syscall
move $t2, $v0 # y
li $v0, 8
la $a0, buf
li $a1, 4008
syscall
la $t3, buf # 读取buf首地址
loop:
add $t4, $t1, $t3
add $t5, $t2, $t3
lbu $t6, 0($t4)
lbu $t7, 0($t5)
sb $t6, 0($t5)
sb $t7, 0($t4)
addi $t1, $t1, 1
addi $t2, $t2, -1
blt $t1, $t2, loop
li $v0, 4
la $a0, buf
syscall
end:
li $v0, 10
syscallT5
.data
aa: .asciiz"A"
bb: .asciiz"B"
cc: .asciiz"C"
to: .asciiz"->"
hh: .asciiz"\n"
.text
li $v0,5
syscall
move $t0, $v0 # n
move $t6, $t0
li $t7, 1
li $t8, 2
li $t9, 3
jal hannuo
li $v0, 10
syscall
hannuo: #参数: k-t6,x-t7,y-t8,z-t9
beq $t6, 1, no
addi $sp, $sp, -20
sw $t6, 0($sp)
sw $t7, 4($sp)
sw $t8, 8($sp)
sw $t9, 12($sp)
sw $ra, 16($sp)
addi $t6, $t6, -1
move $t3, $t9
move $t9, $t8
move $t8, $t3
jal hannuo
lw $t6, 0($sp)
lw $t7, 4($sp)
lw $t8, 8($sp)
lw $t9, 12($sp)
move $t4, $t7
move $t5, $t9
jal print
addi $t6, $t6, -1
move $t3, $t7
move $t7, $t8
move $t8, $t3
jal hannuo
lw $t6, 0($sp)
lw $t7, 4($sp)
lw $t8, 8($sp)
lw $t9, 12($sp)
lw $ra, 16($sp)
addi $sp, $sp, 20
jr $ra
print: # 参数t4, t5
bne $t4, 1, noyi
li $v0, 4
la $a0, aa
syscall
noyi:
bne $t4, 2, noer
li $v0, 4
la $a0, bb
syscall
noer:
bne $t4, 3, nosan
li $v0, 4
la $a0, cc
syscall
nosan:
li $v0, 4
la $a0, to
syscall
bne $t5, 1, noyi1
li $v0, 4
la $a0, aa
syscall
noyi1:
bne $t5, 2, noer1
li $v0, 4
la $a0, bb
syscall
noer1:
bne $t5, 3, nosan1
li $v0, 4
la $a0, cc
syscall
nosan1:
li $v0, 4
la $a0, hh
syscall
jr $ra
no:
addi $sp, $sp, -4
sw $ra, 0($sp)
move $t4, $t7
move $t5, $t9
jal print
lw $ra, 0($sp)
addi $sp, $sp, 4
jr $ra