文章目录:
  1. 数组
  2. 跳转指令
  3. $sp寄存器
  4. 递归
  5. 函数
  6. .data字段
  7. 实验二解答
上一节: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)

其中arrayarray2为设置的数组名字。

加载数组地址:

使用la指令,将某个变量(标签)的地址加载到寄存器中。这是一种伪指令。

la $t0, label

其中,label是数据段中定义的符号名(数组、变量等),la指令把label对应的内存地址存入寄存器$t0

访问数组:

lw:加载字,从内存地址($t0 + offset)取4字节(一个字),放入寄存器$t1offset是字节偏移,通常是用索引乘以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 label

jr指令: 跳转执行寄存器中存储的地址,通常用来返回调用者。最常见用法是从函数返回时跳转回 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跳转

其中后面的几乎都可以使用前两条beqblt指令加上其它比较大小指令(主要是slt)完成。

(slt指令:返回0或1)

slt $at, $t1, $t0    # $at=1 if $t1 < $t0

$sp寄存器

$sp(stack pointer)寄存器用于指向当前栈顶的位置,栈一般用于存储函数调用时的局部变量、保存返回地址、保存调用者保存的寄存器等临时数据。通常,栈是从高地址向低地址生长的,所以 sp 通常会递减以分配空间,递增以释放空间。

基本用法:

因为这个栈是从高地址向低地址生长,所以扩展栈是把栈寄存器的值减字节数,如果释放空间则是把栈寄存器增加字节数。而使用栈的时候,最靠上的位置应该是数越大越靠近内存高位、远离栈顶。

示例: 假设有一个函数 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 

函数

在了解递归后,函数应该很简单。一个函数通常会包含以下过程:

  1. 保存调用者现场(如果有需要)
  2. 保存返回地址寄存器 $ra
  3. 保存被调用者需要保存的寄存器(如 $s0-$s7
  4. 分配栈空间用来存储局部变量
  5. 函数体
  6. 恢复保存的寄存器、返回地址
  7. 恢复栈指针
  8. 返回调用点(使用 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
syscall

T2

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
syscall

T3

.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
syscall

T4

.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
syscall

T5

.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