南京理工大学2023级程序设计基础(II)期末试卷
本文档是2023级程序设计基础(II)期末考试试卷的题解。
一、单项选择
MIPS2000是一种___ CPU
A. 32位CSIC B. 32位RISC C. 64位CISC D. 64位RISC
答案:B
RISC (Reduced Instruction Set Computer,精简指令集计算机);CISC (Complex Instruction Set Computer, 复杂指令集计算机)。MIPS架构是精简指令集架构,而MIPS2000(或称 MIPS R2000)是32位处理器。综上,这道题目应该选择B项,即32位RISC.
用于存放函数返回值的寄存器是___
A.$t0 B. $v0 C. $s0 D. $a0
答案:B
借此题我们可以复习一下CPU中寄存器编号和寄存器的组织,由此表可得到答案$v0,同时也注意寄存器名称与编号的对应。
| 寄存器编号 | 寄存器名称 | 寄存器用途 |
|---|---|---|
$zero | 存储,向$zero写入其他值后,$zero的值仍是 | |
$at | 用于展开宏 | |
$v0,$v1 | 用于存放函数的返回值 | |
$a0, $a1, $a2, $a3 | 用于存放函数的传入参数 | |
$t0, $t1, $t2, ,$t7 | 用于存放不需要保护的值的寄存器 | |
$s0, $s1, $s2, , $s7 | 用于存放需要被保护的值的寄存器,在调用子程序时,需要调用堆栈存储以保护这些寄存器的值 | |
$t8, $t9 | 用于存放不需要保护的值的寄存器 | |
$k0, $k1 | 为操作系统内核保留,用于在异常或中断处理程序中临时存放数据,而不需要在栈上保存和恢复 | |
$gp | 指向静态数据区中间附近的一个位置,通过$gp 加上一个偏移量,可以高效访问内存中数据区的内容 | |
$sp | 指向当前活动栈帧的栈顶,用于管理函数调用时的参数传递、局部变量存储以及返回地址的保存,通常向下增长 | |
$fp | 用于指向当前的活动栈帧的固定位置(考虑到$sp在函数执行中会经常变化),它通常在函数调用时指向函数调用时的$sp,并在函数执行时保持不变 | |
$ra | 用于存放当前子程序的返回地址 |
下列指令中不是I指令格式的是___
A. addi $t1, $t1, 1 B. ble $t0, $t1, L1 C.sw $t1, ($t0) D. srl $t1, $t1, 2
答案:D
借此题我们可以总结一下常见的I格式指令。
首先,对于所有的b(branch),即分支指令,其均为I格式指令,因为它需要计算与其需要跳转的标签的PC差值,作为立即数。
其次,对于所有的末尾带i或iu的指令,它们也都是I格式指令,i作末尾就是Immediate的意思,即需要立即数,而u作为修饰,则表示这是unsigned无符号,忽略溢出错误。
最后,就是存、取指令如lw、sw等,由于这些指令均为基址寻址方式,通过一个寄存器存储基址,用立即数表示偏移量,以方便进行存和取。
务必要注意,所有的左移和右移运算,它们都是R指令,而非I指令!
由于MIPS寄存器仅有32位,其左移、右移的大小最大均只有31,没有必要通过使用16位长度,最大值为65535的立即数来做这种事情,所以左移和右移被编入了R指令中SA(Shift Amount,即偏移量)的部分,仅占5位,提高指令使用效率,如下方所示。
| Op_Code | Rs | Rt | Rd | Sa | Func |
| 0000 00 | 00 000 | 0 0000 | 0000 0 | 000 00 | 00 0000 |
下列运算指令中有可能会产生异常的是___
A. add $a0, $a0, $a0 B. sub $a0, $a0, $a0 C. div $a0, $a0 D. mult $a0, $a0
答案:A、C
此题我认为并不严谨。
对于A选项,当$a0寄存器的值不低于或小于时,其执行完的结果均会出现溢出异常,而对于C选项,若$a0寄存器的值为时,其可能会出现除零异常,这两个选项均有可能产生异常,如果将add改成addu,这道题的答案才为唯一的C。
对于剩余的两个选项,B选项中,执行完后$a0的值确认为,不可能出现溢出的问题,而D选项中,mult虽有可能出现乘法溢出,但由于MIPS使用了HI和LO两个寄存器来存储高32位结果和低32位结果,均不会导致异常。
设$a0中的数是有符号数,计算$a0 = $a0 / 2可用的指令是___
A.sra $a0, $a0, 1 B.srl $a0, $a0, 1 C.sll $a0, $a0, 1 D.div $a0, 2
答案:A
右移分为算术右移和逻辑右移两种,算术右移会在高位直接补对应的符号位,可以直接达到除以2的效果,而逻辑右移则是直接在最高位填0,对于负数,会改变其正负,因此B选项不是正确答案。
对于D选项,这条指令并没有将得到的结果存入$a0中,也就是说,完整的指令应该为:
div $a0, 2
mflo $a0
MIPS分支指令之后的指令位置有一个特定的名称,叫做___
A. 加载延迟槽 B. 空指令槽 C. 分支延迟槽 D. 废弃指令槽
答案:C
A选项加载延迟槽用于lw、lb等指令,用于保证在执行下一条指令时,相应内存地址的内容已被加载到寄存器中,C选项类似。
将$t0的低4位取反,其他位不变的指令是___
A. xori $t0, $t0, 0x0F B. nor $t0, $t0, $0 C. ori $t0, $t0, 0x0F D. andi $t0, $t0, 0x0F
答案:A
我们可以把几个位运算在这里进行总结。
| 位运算名称 | 指令 | 运算规则 |
|---|---|---|
| 按位与 | 以and开头的若干指令,如and,andi,andiu等,后续不赘述 | 同一位上均为1,结果中对应位为1,否则为0 |
| 按位或 | 以or开头的若干指令 | 同一位上均为0,结果中对应位为0,否则为1 |
| 按位或非 | 以nor开头的若干指令,其实际意义为或非NOT OR | 同一位上均为0,结果中对应位为1,否则为0 |
| 按位异或 | 以xor开头的若干指令 | 同一位上不同,结果中对应位为1,否则为0 |
同时进行取反和保留其他位,这符合异或的运算方式,异或1为取反,异或0为保留。表中未提及的指令通常是一些伪指令,通过宏展开实现,可自行查阅书后附录复习。
一个函数的栈帧中不包含___
A. 该函数的参数 B. 临时寄存器 C. 分配给该函数定义的局部变量 D. 全局变量
答案:D
A选项在函数的参数超过4个时,需要通过栈空间保存以完成传参;B选项同理,在函数中再次调用其他函数时,需要通过栈空间保护临时寄存器中的内容;C选项,函数中的局部变量,为了保证其在函数运行结束后(或代码块结束时)不被其他函数非法调用,也会存入栈中,只有D选项全局变量可以直接通过数据段存储,不应当包含在栈帧中。
为了更好的解释C选项,参考如下C代码,这应该也能更好解释C语言中作用域的问题:
int Global_Int; // <- This is NOT Saved in Stack
void Sample_Function(int Argument_1, int Argument_2)
{
int Temporary_Int_1; // <- This is Saved in Stack
{
int Temporary_Int_2; // <- This is Also Saved in Stack
}
// Temporary_Int_2 Vanished
}
// Temporary_Int_1 Vanished
MIPS系统异常发生时的硬件动作,最后一步是___
A. CPU从异常处理入口地址取指令,开始执行异常处理程序 B. 将CPU切换到权限更高的系统态,并禁止响应中断 C. 设置Cause寄存器,使软件可以看到异常的原因 D. 设置EPC,使其指向返回时重新执行的指令
答案:A
异常处理的流程图如下:
如果需要用一些更形象的语言表达这个过程,可以看如下的解释。 假如你现在遇到了一场交通事故,第一步需要先记录下出事点的位置,也就是将出现异常的程序计数器记录到**EPC(Exception Program Counter)**中,对应D选项。
第二步,记录为什么出事,CPU会记录下到底发生什么类型的异常,将一个代表异常原因的代码存到Cause寄存器中,保证软件可以看到发生异常的原因,对应C选项。 第三步,改变CPU的工作状态,即设置Status寄存器,将CPU切换到内核态,权限更高,并禁止其他中断,保证先处理掉这个事故,对应B选项。
第四步,进入“急诊室”,进入异常处理程序,对应A选项。
以下表格总结了MIPS异常处理中主要用到的寄存器及其用途,其中标明编号的是协处理器0中的寄存器编号:
| 寄存器名称 (MIPS) | 完整名称/类别 (中文) | 主要用途 (在异常处理中) |
|---|---|---|
| EPC(14号) | Exception Program Counter (异常程序计数器) | 硬件保存发生异常时指令的地址或返回地址,供异常处理完毕后恢复执行。 |
| Cause(13号) | Cause Register (原因寄存器) | 硬件记录异常发生的原因代码 (ExcCode) 和其他相关信息 (如中断状态、是否在分支延迟槽)。 |
| Status (SR)(12号) | Status Register (状态寄存器) | 硬件修改该寄存器以进入内核态、禁止/使能中断、指示异常级别 (EXL/ERL);软件也用它来恢复状态或控制中断。 |
| BadVAddr(8号) | Bad Virtual Address (坏虚地址寄存器) | 当发生地址相关异常 (如TLB未命中、地址错) 时,硬件保存导致异常的虚拟地址。 |
| $k0, $k1 | Kernel Reserved Registers (内核保留通用寄存器) | 供操作系统内核/异常处理程序用作临时存储,无需事先保存用户态的值。 |
| PC | Program Counter (程序计数器) | 硬件在异常发生时强制将其设置为异常处理程序的入口地址;异常返回时,软件用EPC的值恢复PC。 |
| CP0 其他寄存器 | System Control Coprocessor (系统控制协处理器其他寄存器) | 根据具体异常类型使用,例如:Context, EntryHi, EntryLo 等用于TLB管理 Count, Compare 用于定时器中断 WatchLo, WatchHi 用于硬件断点 |
二、填空题
1.在下列程序片段中,宏指令ble $t1, $t0, insert的实现代码是__(1)__(要求使用基本指令slt和beq实现)。假设执行该程序片段时,标号begin代表的地址是0x00400000.
begin: addiu $t0, $0, 200
again: lh $t1, ($a0)
ble $t1, $t0, insert
sh $t1, 2($a0)
addi $a0, $a0, -2
bgez $0, again
insert: sh $t0, 2($a0)
寄存器$a0中的值为0x10010004,内存如下所示。

指令sh $t1, 2($a0)的首次执行过程如下:
(1) IF:根据PC值到内存__(2)__处取出指令机器码__(3)__并放到指令寄存器IR中,PC增量4;(提示:sh Rt, Offset(Rs)的指令格式为
,其中操作码为101001,寄存器$a0的编号为4,寄存器$t1的编号为9)
(2)RD:解析IR中指令,采用__(4)__寻址方式定位存储单元地址,读取寄存器$a0中的内容0x10010004放到运算器的一个输入数据总线上,读取16位立即数0x0002放到运算器的另一个输入数据总线上。读取寄存器堆中$t1内容__(5)__放到数据缓存的输入数据总线上;
(3)ALU:ALU做加法运算,得到写入的内存地址0x10010006;
(4)Mem:把数据缓存的输入数据总线上的数据放到内存中,按照“高位高地址,低位低地址”存放数据的内存空间为__(6)__(要求图示,画出相应内存图,并给出地址,分配和存放值)
在程序执行过程中,当寄存器$t1中的值为__(7)__时,执行标号insert处的指令sh $t0, 2($a0),该指令执行后,内存空间为__(8)__.
**答案:(1) **
slt $t2, $t0, $t1
#第一行中$t2不唯一,可以写成其他任何未被使用的$t3-$t9寄存器,也可写专用于宏展开的寄存器$at
beq $t2, 0, insert
# 第二行答案不唯一,也可以是如下的内容
beq $t2, $zero, insert
slt Rd, Rs, Rt指令的执行后,会检查Rs和Rt的大小,若,则将Rd寄存器设置为1,否则设置为0.
这里ble指令需要用slt实现,我们考虑如果不成立,则有。
那么代入式子中,我们需要有的是$t1$t0,那么就需要将Rt设置为$t1, Rs设置为$t0,以达成我们的目标,当其不成立时,$t0 $t1,此时$t2被设置为0,随后使用分支指令以完成跳转。
(2) 0x00400010
由(1)问已经得知ble $t1, $t0, insert实际展开后有两个指令,那么由begin标签开始计算内存地址,lh $t1, ($a0)的地址为0x00400004,ble $t1, $t0, insert展开后需要两个地址0x00400008、0x0040000c,那么sh $t1, 2($a0)的地址就应当是0x00400010
(3) 0xa4890002
见下图
| Op_Code | Rs | Rt | Offset |
| 1010 01 | 00 100 | 0 1001 | 0000 0000 0000 0010|
a 4 8 9 0 0 0 2
(4) 基址 (5) 0x012c
(4)空考概念,这里不做额外解释,(5)空注意题目中标红的内容,通过**高位高地址,低位低地址**的规则在内存图中找到起始地址0x10010004将需要的数值拼出。
(6) 如下图所示

(7) 0x0064
理解程序意图,这段程序本质上是插入排序,从上(后)往下(前)依次找不大于目标数值的数,比目标数值大的数将被向后移位存储,直到找到比目标数值小的数,停止并将目标数值存储到挪出来的空中。
我们可以将这段内存中存储的半字的值列出来,0x10010004中存储的半字值为300,0x10010002中存储的值为100,0x10010000中存储的值为-1,那么程序执行到sh $t0, 2($a0)时,说明已经找到了不大于欲插入的值200的值100,也就是0x0064,并将200存到挪出的空中,对应第(8)空的答案。
(8) 如下图所示

2.读下列MIPS32汇编语言代码并回答问题。
.data
prompt: .asciiz "Hello World\n"
.text
main: li $v0, 4
la $a0, prompt
syscall
li $t0, 0
li $t1, 0x0a
loop1: lb $t2, ($a0)
beq $t2, $t1, next
addi $a0, $a0, 1
addi $t0, $t0, 1
b loop1
next: la $a1, prompt
add $a2, $a1, $t0
loop2: lb $a0, ($a2)
addi $a2, $a2, -1
li $v0, 11
syscall
bge $a2, $a1, loop2
li $v0, 10
syscall
当程序执行到标号next处时,寄存器$t0的值为__(9)__,寄存器$t1的值为__(10)__,寄存器$t2的值为__(11)__,最终输出结果是__(12)__.
答案:(9) 11 (10) 0x0a 或 10 或 '\n' (11)0x0a 或 10 或 '\n' (12) 如下
Hello World
dlroW olleH
对代码解读如下:
.data
prompt: .asciiz "Hello World\n"
.text
main: li $v0, 4
la $a0, prompt # 输出字符串"Hello World\n"
syscall
li $t0, 0 # 初始化字符计数器
li $t1, 0x0a # 将$t1设置为0x0a,即'\n'
loop1: lb $t2, ($a0) # 将$a0地址的值读出并存到$t2中
beq $t2, $t1, next # 如果$t2是换行符,则已读到末尾,进入next
addi $a0, $a0, 1 # 向后移动1个字节以准备读入下一字符
addi $t0, $t0, 1 # 增加读到的总字符数
b loop1
next: la $a1, prompt # 此时,$t0 = 11,并设置$a1为prompt地址,其指向'H'
add $a2, $a1, $t0 # 设置$a2指向'\n'(末尾字符)
loop2: lb $a0, ($a2) # 加载$a2地址中的字符
addi $a2, $a2, -1 # $a2向前移动
li $v0, 11 # 输出$a2地址中的字符
syscall
bge $a2, $a1, loop2 # 若$a2不小于$a1,则继续循环
li $v0, 10 # 结束程序
syscall
其等价于如下的C代码:
#include <stdio.h>
const char * prompt = "Hello World\n";
int main()
{
const char * a0 = prompt;
printf("%s", a0);
int t0 = 0;
char t1 = 0x0a;
while (*a0 != t1)
{
a0++;
t0++;
}
const char * a1 = prompt;
const char * a2 = a1 + t0;
do
{
printf("%c", *a2);
a2--;
}while (a2 >= a1);
return 0;
}
3.在下列程序片段1中,标号msg代表的地址是0x10010000,当执行指令__(13)__时发生异常,此时协处理器0中8号寄存器BadVaddr的值是 __(14)__,在划线处增加指令__(15)__可避免异常;若要输出程序片断1执行过程中的异常码,可在程序片段2中的划线处增加指令__(16)__.
# 程序片段1
.data
msg: .ascii "ExcCode"
_________
num: .word 1, 2, 3, 4, 5
.text
main: la $a0, num
sw $t0, 4($a0)
# 程序片段2
_________
andi $a0, $k0, 0x7c
syscall
**答案:(13) **sw $t0, 4($a0) (14) 0x1001000B (15).align 2 (16)mfc0 $k0, $13
这段代码会出现错误的原因是在长度为7的msg字符串后并未使内存对齐(这里有一个前提,MARS和SPIM的编译器选项中并不会自动使word型对齐),从而导致在必须按字对齐地址存储值的sw $t0, 4($a0)中的目标地址出现错误。而BadVaddr的值便是出现异常的地址,即$a0 + 4 = 0x1001000B,在划线处增加.align 2使内存对齐至字节,即可规避这个异常。对于(16)空,请参考如下的解读:
"异常码"(Exception Code)是一个数字,用于标识异常的类型。它通常存储在MIPS协处理器0(CP0)的13号寄存器——Cause寄存器的ExcCode字段(通常是bit 6到bit 2)。例如,存储时地址错误(AdES)的ExcCode是5。
程序片段2的结构暗示了后续的 addi 和 syscall 指令是一个已有的机制(可能是操作系统提供的一个服务例程)。指令(16)需要为这个机制准备输入。
$k0和$k1是内核保留的临时寄存器,异常处理程序(属于内核代码)经常使用它们。- 假设片段2中的
syscall调用了一个特定的操作系统例程来显示异常信息。这个例程可能期望在$k0寄存器中接收到原始的Cause寄存器内容。然后,操作系统例程会自行从$k0中的Cause数据里解析出5位的ExcCode并进行显示。addi $a0, $k0, 0x7c指令可能是为这个系统调用设置另一个参数。
因此,指令(16)最合理的选择是将 Cause 寄存器的内容移动到 $k0:
mfc0 $k0, $13:这条指令的含义是 "Move From Coprocessor 0",它将CP0的13号寄存器(Cause寄存器)的内容复制到通用寄存器$k0中。
这样,后续的 addi 和 syscall 指令就可以利用 $k0 中未经处理的 Cause 寄存器信息以及 $a0 中由 $k0 + 0x7c 计算得到的值,来完成输出具体异常码的任务。
4.下述程序中,输入整数N,使用乘法和移位操作求1到N之和并输出,函数SUM接收寄存器$a0传递的N,结果通过寄存器$v0返回。请在程序空白处填写适当的MIPS汇编指令或伪指令(指示性语句)。
.data
prompt: .asciiz "\n Please input an integer less than 65536"
result: .asciiz "\n The sum is: "
mess_e: .asciiz "\n The integer is more than 65535!"
.text
main: li $v0, 4 # 输出提示语句
la $a0, prompt
syscall
li $v0, 5 # 输入N
syscall
____(17)____ # 传递参数后调用SUM
jal SUM
move $a1, $v0
bltz $a1, out_e # 返回值为-1时报错
li $v0, 4
la $a0, result
syscall
move $a0, $a1
li $v0, 1
syscall
b exit
out_e: li $v0, 4 # 输出错误提示信息
____(18)____
syscall
exit: li $v0, 10
syscall
SUM: li $v0, -1
li $t9, 65535
bgt $a0, $t9, ret
addiu $a1, $a0, 1
____(19)____ # 判断$a0奇偶性
beqz $t0, even
sra $a1, $a1, 1 # 计算(N + 1)/ 2
____(20)____
even: ra $a0, $a0, 1 # 计算N / 2
next: mulo $v0, $a0, $a1 # 计算N (N + 1) / 2
ret: jr $ra
答案:(17)move $a0, $v0 (18)la $a0, mess_e (19)andi $t0, $a0, 0x0001 (20)b next
对(19)空而言,由于后文中使用了
beqz来检查我们存入$t0寄存器的值,那么最简单的办法就是通过位运算检查$a0寄存器的最后一位是否是1,屏蔽其他位并保留最后一位的方法是按位与运算,所以此处填入andi $t0, $a0, 0x0001
三、程序翻译题
1.将下列汇编语言程序翻译成C语言,并描述其功能。
.data
string: .asciiz "aBCd"
.text
main: la $a0, string
li $t0, 0x41
li $t1, 0x5a
again: lb $t2, ($a0)
beqz $t2, ret
blt $t2, $t0, next
bgt $t2, $t1, next
addi $t2, $t2, 0x20
sb $t2, ($a0)
next: addi $a0, $a0, 1
b again
ret: li $v0, 10
syscall
答案:(仅供参考)
char str[5] = {'a', 'B', 'C', 'd', '\0'};
int main()
{
char * a0 = string;
char t0 = 0x41; // t0 = 'A'
char t1 = 0x5a; // t1 = 'Z'
do
{
char t2 = *a0;
if (t2 >= t0 && t2 <= t1)
{
t2 += 0x20; // Convert to Lower_Case
*a0 = t2;
}
a0++;
}while(*a0);
return 0;
}
功能:将字符串中的大写字母转为小写字母
2.将下列汇编语言函数翻译成C函数,并写出函数接口和功能说明。
strncpy: li $v0, 0
loop: lb $t0, ($a0)
addi $a0, $a0, 1
sb $t0, ($a1)
addi $a1, $a1, 1
beqz $t0, ret
add $v0, $v0, 1
blt $v0, $a2, loop
sb $0, ($a1)
ret: jr $ra
答案:(仅供参考)
int strncpy(const char * a0, char * a1, int a2)
{
int v0 = 0;
do
{
char t0 = *a0;
a0++;
*a1 = t0;
a1++;
if (!(*a0))
{
break;
}
v0++;
}while (v0 < a2);
if (v0 == a2)
{
*a1 = 0;
}
return v0;
}
函数接口:传入参数为原始字符串指针$a0,目标字符串指针$a1,和最大复制长度$a2,返回值$v0为成功复制的字符数量。
功能说明:将$a0字符串中最多$a2个字符复制到$a1字符串中。
3.将下列C代码翻译成MIPS汇编,使用堆栈传递函数参数和返回值,局部变量使用寄存器。
// 获得数组a中相互不等的元素,放入数组rs中,并返回个数
int getSet(int rs[], int a[], int n)
{
int i, j, k;
for (i = 0, j = 0; i < n; i++)
{
for (k = 0; k < j; k++)
{
if (a[i] == rs[k])
{
break; // 在rs中查找到了a[i],中断循环
}
}
if (k == j) // 在rs中没有查找到a[i],记录a[i]到rs中
{
rs[j] = a[i];
j++;
}
}
return j;
}
答案:(仅供参考)
# 根据MIPS汇编语言的堆栈传递规范,在函数开始执行时,($sp)指向的地址自下而上应当分别对应rs[], a[], n, ret_Value, 和发起调用的函数的$ra
getSet: lw $t9, ($sp) # 取rs地址至$t9,作为数组尾部
lw $t8, 4($sp) # 取数组a起始地址至$t8
lw $t7, 8($sp) # 取数组元素个数n至$t7
xor $t0, $t0, $t0 # 初始化i
xor $t1, $t1, $t1 # 初始化j
gS_Loop: lw $t2, ($t8) # 取待查的a数组元素
xor $t3, $t3, $t3 # 初始化k
lw $t4, ($sp) # 从rs数组头开始检查
beqz $t1, gS_Not_Found # 检查初始条件,j=0时不应进入循环
gS_Loop_2: lw $t5, ($t4) # 取Rs的数组元素
beq $t2, $t5, gS_Found # 检查到对应元素则break
addi $t4, $t4, 4 # 检查下一个元素
addi $t3, $t3, 1 # 更新k
blt $t3, $t1, gS_Loop_2
gS_Not_Found:
sw $t2, ($t9) # 存储不存在的元素
addi $t1, $t1, 1 # 更新j
addi $t9, $t9, 4 # 数组尾部后移
gS_Found: addi $t8, $t8, 4 # 向后检查a数组的其他元素
addi $t0, $t0, 1 # 更新i
blt $t0, $t7, gS_Loop # 若未检查完a数组,继续检查
sw $t1, 12($sp) # 存储返回值
jr $ra
四、编程题
1.编写一个函数Bin2Hex,将无符号数转换成以0x开头的十六进制值,无符号数通过寄存器$a0传递,十六进制值的字符串存放地址通过$a1传递。
Bin2Hex: li $t0, '0' # 先将"0x"存到输出字符串的开始
sb $t0, ($a1)
addi $a1, $a1, 1
li $t0, 'x'
sb $t0, ($a1)
addi $a1, $a1, 9 # 在字符串的末尾存'\0'标志字符串结束
sb $zero, ($a1)
addi $a1, $a1, -1 # 由末尾向开头存数
li $t0, 8 # 不论如何均以8位16进制数输出,故进行8次拆解
Loop: andi $t1, $a0, 0x0f # 取末4位
bge $t1, 10, Alphabet # 检查是否需要用字母'a'-'f'表达
addi $t1, $t1, 48 # 用数字'0'-'9'表达
b Save
Alphabet: addi $t1, $t1, 87 # 转化为对应字母
Save: sb $t1, ($a1) # 保存当前位
addi $a1, $a1, -1 # 更新字母存储地址
addi $t0, $t0, -1 # 更新循环计数
sra $a0, $a0, 4 # 将目标数右移4位以进行再次拆解
bgtz $t0, Loop # 若未完成8位填数,则继续拆解
jr $ra
2.写一个完整的可重入程序,先输入N,再输入N个整数,按从小到大排序后输出。
.data
space: .asciiz " "
.align 2
.globl main
.text
main: li $v0, 5 # 输入数组大小
syscall
blez $v0, end # 若输入的大小不是正整数,退出程序
move $t0, $v0 # 将输入的整数转到$t0寄存器中
sll $t8, $t0, 2 # 左移$t0,给出开辟栈空间的大小
subu $sp, $sp, $t8 # 开辟栈空间存储数组
move $t2, $sp # 加载数组地址
xor $t1, $t1, $t1 # 初始化循环变量
Input_Loop:
addi $t1, $t1, 1 # 循环计数控制
li $v0, 5 # 读入数字
syscall
sw $v0, ($t2) # 存入数组
addi $t2, $t2, 4 # 移到下一个需要存数的地址
blt $t1, $t0, Input_Loop # 若未完成读入则继续
addi $t2, $t0, -1 # 冒泡排序外层循环计数
Sort_Loop: move $t3, $t2 # 冒泡排序内层循环计数
move $t4, $sp # 加载数组地址
Sort_Loop_2:
lw $t5, ($t4) # 加载数组元素
lw $t6, 4($t4) # 加载下一个数组元素
blt $t5, $t6, No_Swap # 比较以确认是否需要交换
sw $t6, ($t4) # 交换
sw $t5, 4($t4)
No_Swap: addi $t3, $t3, -1 # 更新循环计数
addi $t4, $t4, 4 # 移到下一个需要检查的地址
bgtz $t3, Sort_Loop_2 # 若未完成本轮冒泡则继续
addi $t2, $t2, -1 # 更新外层循环计数
bgtz $t2, Sort_Loop # 若未完成所有轮次冒泡则继续下一轮
move $t2, $sp # 加载数组地址
xor $t3, $t3, $t3 # 初始化循环计数
Output_Loop:
lw $a0, ($t2) # 输出数
li $v0, 1
syscall
la $a0, space # 输出空格以分隔
li $v0, 4
syscall
addi $t3, $t3, 1 # 检查是否输出结束
addi $t2, $t2, 4
blt $t3, $t0, Output_Loop
addu $sp, $sp, $t8 # 回收栈空间
end: li $v0, 10 # 结束程序
syscall
