发布于 2025/6/6 · 标签:程序设计基础MIPS汇编语言期末试卷

← 返回归档

本文档是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,同时也注意寄存器名称与编号的对应。

寄存器编号寄存器名称寄存器用途
00$zero存储00,向$zero写入其他值后,$zero的值仍是00
11$at用于展开宏
232\to3$v0,$v1用于存放函数的返回值
474\to7$a0, $a1, $a2, $a3用于存放函数的传入参数
8158\to15$t0, $t1, $t2, \cdots,$t7用于存放不需要保护的值的寄存器
162316\to23$s0, $s1, $s2, \cdots, $s7用于存放需要被保护的值的寄存器,在调用子程序时,需要调用堆栈存储以保护这些寄存器的值
242524\to25$t8, $t9用于存放不需要保护的值的寄存器
262726\to27$k0, $k1为操作系统内核保留,用于在异常或中断处理程序中临时存放数据,而不需要在栈上保存和恢复
2828$gp指向静态数据区中间附近的一个位置,通过$gp 加上一个偏移量,可以高效访问内存中数据区的内容
2929$sp指向当前活动栈帧的栈顶,用于管理函数调用时的参数传递、局部变量存储以及返回地址的保存,通常向下增长
3030$fp用于指向当前的活动栈帧的固定位置(考虑到$sp在函数执行中会经常变化),它通常在函数调用时指向函数调用时的$sp,并在函数执行时保持不变
3131$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差值,作为立即数。

其次,对于所有的末尾带iiu的指令,它们也都是I格式指令,i作末尾就是Immediate的意思,即需要立即数,而u作为修饰,则表示这是unsigned无符号,忽略溢出错误。

最后,就是存、取指令如lwsw等,由于这些指令均为基址寻址方式,通过一个寄存器存储基址,用立即数表示偏移量,以方便进行存和取。

务必要注意,所有的左移和右移运算,它们都是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寄存器的值不低于2302^{30}或小于230-2^{30}时,其执行完的结果均会出现溢出异常,而对于C选项,若$a0寄存器的值为00时,其可能会出现除零异常,这两个选项均有可能产生异常,如果将add改成addu,这道题的答案才为唯一的C。

对于剩余的两个选项,B选项中,执行完后$a0的值确认为00,不可能出现溢出的问题,而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选项加载延迟槽用于lwlb等指令,用于保证在执行下一条指令时,相应内存地址的内容已被加载到寄存器中,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

异常处理的流程图如下:

Loading Diagram...

如果需要用一些更形象的语言表达这个过程,可以看如下的解释。 假如你现在遇到了一场交通事故,第一步需要先记录下出事点的位置,也就是将出现异常的程序计数器记录到**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, $k1Kernel Reserved Registers (内核保留通用寄存器)供操作系统内核/异常处理程序用作临时存储,无需事先保存用户态的值。
PCProgram Counter (程序计数器)硬件在异常发生时强制将其设置为异常处理程序的入口地址;异常返回时,软件用EPC的值恢复PC。
CP0 其他寄存器System Control Coprocessor (系统控制协处理器其他寄存器)根据具体异常类型使用,例如:
Context, EntryHi, EntryLo 等用于TLB管理
Count, Compare 用于定时器中断
WatchLo, WatchHi 用于硬件断点

二、填空题

1.在下列程序片段中,宏指令ble $t1, $t0, insert的实现代码是__(1)__(要求使用基本指令sltbeq实现)。假设执行该程序片段时,标号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,内存如下所示。

image-20250605071932069

初始内存图

​ 指令sh $t1, 2($a0)的首次执行过程如下:

​ (1) IF:根据PC值到内存__(2)__处取出指令机器码__(3)__并放到指令寄存器IR中,PC增量4;(提示:sh Rt, Offset(Rs)的指令格式为image-20250605072435479,其中操作码为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指令的执行后,会检查RsRt的大小,若Rs<RtRs \lt Rt,则将Rd寄存器设置为1,否则设置为0.

这里ble指令需要用slt实现,我们考虑Rs<RtRs\lt Rt如果不成立,则有RsRtRs\geq Rt

那么代入式子中,我们需要有的是$t1\leq$t0,那么就需要将Rt设置为$t1, Rs设置为$t0,以达成我们的目标,当其不成立时,$t0 <\lt$t1,此时$t2被设置为0,随后使用分支指令以完成跳转。

(2) 0x00400010

由(1)问已经得知ble $t1, $t0, insert实际展开后有两个指令,那么由begin标签开始计算内存地址,lh $t1, ($a0)的地址为0x00400004ble $t1, $t0, insert展开后需要两个地址0x004000080x0040000c,那么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) 如下图所示

image-20250605075628321

(7) 0x0064

理解程序意图,这段程序本质上是插入排序,从上(后)往下(前)依次找不大于目标数值的数,比目标数值大的数将被向后移位存储,直到找到比目标数值小的数,停止并将目标数值存储到挪出来的空中。

我们可以将这段内存中存储的半字的值列出来,0x10010004中存储的半字值为3000x10010002中存储的值为1000x10010000中存储的值为-1,那么程序执行到sh $t0, 2($a0)时,说明已经找到了不大于欲插入的值200的值100,也就是0x0064,并将200存到挪出的空中,对应第(8)空的答案。

(8) 如下图所示

image-20250605080357087

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使内存对齐至22=42^2=4字节,即可规避这个异常。对于(16)空,请参考如下的解读:

"异常码"(Exception Code)是一个数字,用于标识异常的类型。它通常存储在MIPS协处理器0(CP0)的13号寄存器——Cause寄存器的ExcCode字段(通常是bit 6到bit 2)。例如,存储时地址错误(AdES)的ExcCode是5。 程序片段2的结构暗示了后续的 addisyscall 指令是一个已有的机制(可能是操作系统提供的一个服务例程)。指令(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 中。

这样,后续的 addisyscall 指令就可以利用 $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