简介
咱们知道x86架构cpu用于PC端和工作站较多,ARM架构cpu常见于手机和单片机,那么MIPS架构的cpu主要在哪些设备可以找到它们的身影呢?
- 中国龙芯
- PS游戏机
学习环境搭建
- 安装JDK, 主要用于运行mips模拟器mars
- MARS模拟器:https://courses.missouristate.edu/KenVollmar/mars/download.htm
寄存器
在mips中通用寄存器用$开头表示,一共有32个
寄存器编号 | 寄存器名 | 寄存器用途 |
---|---|---|
$0 | $zero | 永远返回0 |
$1 | $at | 保留寄存器 |
$2-$3 | $v0-$v1 | 一般用于存储表达式或者函数的返回值(value的简写) |
$4-$7 | $a0-$a3 | 参数寄存器(Argument简写) |
$8-$15 | $t0-$t7 | 一般用于存储临时变量(temp简写) |
$16-$23 | $s0-$s7 | 存放子函数调用过程需要被保留的数据(saved values) |
$24-$25 | $t8-$t9 | 属性同$t0-$t7 |
$26-$27 | $k0-$k1 | 一般存储中断函数返回值 |
$28 | $gp | GlobalPointer简写 |
$29 | $sp | 栈指针,指向栈顶(Stack Pointer简写) |
$30 | $s8/$fp | (Save / Frame Pointer)帧指针 |
$31 | $ra | 一般用于存储函数返回地址(return address简写) |
寄存器编号和别名一一对应,同一个寄存器可以有两种不同表示方法:0或者zero
- program counter (PC) 无法直接修改,通过跳转指令可以改动
- HI 和 LO :这两个寄存器特别用来保存乘法、除法、乘法累加的结果。
MIPS汇编中的分段处理
代码语言:javascript复制.data #数据段
.text #代码段
传送指令
- 加载立即数指令
li
li
(load immediate) :用于将立即数传送给寄存器
li $t0,1 ;十六进制数据使用0x前缀表示
- 加载地址指令
la
la
(load address) :用于将地址传送至寄存器中, 多用于通过地址获取数据段中的地址
.data
msg: .ascii "hello world"
.text
la $a0,msg # 将字符串数据所在的地址赋值给$a0寄存器
- 寄存器数据传送指令
move
用于将一个寄存器中的数据传送至另一个寄存器当中
代码语言:javascript复制move $t0,$t1 # 将寄存器$t1中的数据传送至$t0
系统服务指令 syscall
在C语言中输出文本可以使用printf
函数,但是汇编中没有printf这么一说,如果想要输出文本,需要借助syscall
指令
如果想要输出一个数字1,那么syscall
指令从$a0寄存器中取出需要输出的数据
因此, 你在执行syscall
指令之前需要将数据提前放入$a0
之中:
li $a0,1
syscall
同时,还需要指定输出的数据类型,数据类型的指定保存在$v0寄存器中
代码语言:javascript复制# $v0=1, syscall--->print_int
# $v0=4, syscall--->print_string
v0存入1,表示syscall将a0中的数据当做数字输出
v0存入4,表示syscall将a0中的数据当做数据的地址,然后输出对应的数据
syscall指令读写对照表
Service | Code in $v0 | Arguments | Result |
---|---|---|---|
print integer | 1 | $a0 = integer to print | |
print float | 2 | $f12 = float to print | |
print double | 3 | $f12 = double to print | |
print string | 4 | $a0 = address of null-terminated string to print | |
read integer | 5 | $v0 contains integer read | |
read float | 6 | $f0 contains float read | |
read double | 7 | $f0 contains double read | |
read string | 8 | $a0 = address of input buffer $a1 = maximum number of characters to read | See note below table |
sbrk (allocate heap memory) | 9 | $a0 = number of bytes to allocate | $v0 contains address of allocated memory |
exit (terminate execution) | 10 | ||
print character | 11 | $a0 = character to print | See note below table |
read character | 12 | $v0 contains character read | |
open file | 13 | $a0 = address of null-terminated string containing filename $a1 = flags $a2 = mode | $v0 contains file descriptor (negative if error). See note below table |
read from file | 14 | $a0 = file descriptor $a1 = address of input buffer $a2 = maximum number of characters to read | $v0 contains number of characters read (0 if end-of-file, negative if error). See note below table |
write to file | 15 | $a0 = file descriptor $a1 = address of output buffer $a2 = number of characters to write | $v0 contains number of characters written (negative if error). See note below table |
close file | 16 | $a0 = file descriptor | |
exit2 (terminate with value) | 17 | $a0 = termination result | See note below table |
Services 1 through 17 are compatible with the SPIM simulator, other than Open File (13) as described in the Notes below the table. Services 30 and higher are exclusive to MARS. | |||
time (system time) | 30 | $a0 = low order 32 bits of system time $a1 = high order 32 bits of system time. See note below table | |
MIDI out | 31 | $a0 = pitch (0-127) $a1 = duration in milliseconds $a2 = instrument (0-127) $a3 = volume (0-127) | Generate tone and return immediately. See note below table |
sleep | 32 | $a0 = the length of time to sleep in milliseconds. | Causes the MARS Java thread to sleep for (at least) the specified number of milliseconds. This timing will not be precise, as the Java implementation will add some overhead. |
MIDI out synchronous | 33 | $a0 = pitch (0-127) $a1 = duration in milliseconds $a2 = instrument (0-127) $a3 = volume (0-127) | Generate tone and return upon tone completion. See note below table |
print integer in hexadecimal | 34 | $a0 = integer to print | Displayed value is 8 hexadecimal digits, left-padding with zeroes if necessary. |
print integer in binary | 35 | $a0 = integer to print | Displayed value is 32 bits, left-padding with zeroes if necessary. |
print integer as unsigned | 36 | $a0 = integer to print | Displayed as unsigned decimal value. |
(not used) | 37-39 | ||
set seed | 40 | $a0 = i.d. of pseudorandom number generator (any int). $a1 = seed for corresponding pseudorandom number generator. | No values are returned. Sets the seed of the corresponding underlying Java pseudorandom number generator (java.util.Random). See note below table |
random int | 41 | $a0 = i.d. of pseudorandom number generator (any int). | $a0 contains the next pseudorandom, uniformly distributed int value from this random number generator’s sequence. See note below table |
random int range | 42 | $a0 = i.d. of pseudorandom number generator (any int). $a1 = upper bound of range of returned values. | $a0 contains pseudorandom, uniformly distributed int value in the range 0 = [int] [upper bound], drawn from this random number generator’s sequence. See note below table |
random float | 43 | $a0 = i.d. of pseudorandom number generator (any int). | $f0 contains the next pseudorandom, uniformly distributed float value in the range 0.0 = f 1.0 from this random number generator’s sequence. See note below table |
random double | 44 | $a0 = i.d. of pseudorandom number generator (any int). | $f0 contains the next pseudorandom, uniformly distributed double value in the range 0.0 = f 1.0 from this random number generator’s sequence. See note below table |
(not used) | 45-49 | ||
ConfirmDialog | 50 | $a0 = address of null-terminated string that is the message to user | $a0 contains value of user-chosen option 0: Yes 1: No 2: Cancel |
InputDialogInt | 51 | $a0 = address of null-terminated string that is the message to user | $a0 contains int read $a1 contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field |
InputDialogFloat | 52 | $a0 = address of null-terminated string that is the message to user | $f0 contains float read $a1 contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field |
InputDialogDouble | 53 | $a0 = address of null-terminated string that is the message to user | $f0 contains double read $a1 contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field |
InputDialogString | 54 | $a0 = address of null-terminated string that is the message to user $a1 = address of input buffer $a2 = maximum number of characters to read | See Service 8 note below table $a1 contains status value 0: OK status. Buffer contains the input string. -2: Cancel was chosen. No change to buffer. -3: OK was chosen but no data had been input into field. No change to buffer. -4: length of the input string exceeded the specified maximum. Buffer contains the maximum allowable input string plus a terminating null. |
MessageDialog | 55 | $a0 = address of null-terminated string that is the message to user $a1 = the type of message to be displayed: 0: error message, indicated by Error icon 1: information message, indicated by Information icon 2: warning message, indicated by Warning icon 3: question message, indicated by Question icon other: plain message (no icon displayed) | N/A |
MessageDialogInt | 56 | $a0 = address of null-terminated string that is an information-type message to user $a1 = int value to display in string form after the first string | N/A |
MessageDialogFloat | 57 | $a0 = address of null-terminated string that is an information-type message to user $f12 = float value to display in string form after the first string | N/A |
MessageDialogDouble | 58 | $a0 = address of null-terminated string that is an information-type message to user $f12 = double value to display in string form after the first string | N/A |
MessageDialogString | 59 | $a0 = address of null-terminated string that is an information-type message to user $a1 = address of null-terminated string to display after the first string | N/A |
使用syscall指令输出helloworld示例:
代码语言:javascript复制.data
msg: .ascii "hello world " #类似于C语言中 char* msg="hello world"
.text
la $a0,msg
li $v0,4
syscall
数据定义
定义整型数据
定义Float数据
定义Double数据
定义字符串数据
用户输入
字符串输入
整型数据输入
浮点型数据输入
单精度和双精度
单精度数(float型)在32位计算机中存储占用4字节,也就是32位,有效位数为7位,小数点后6位。
双精度数(double型)在32位计算机中存储占用8字节,也就是64位,有效位数为16位,小数点后15位。
浮点寄存器
在mips中一共有32个浮点寄存器(其中包含16个双精度浮点寄存器),用于单独处理浮点数
函数声明和调用
函数声明
- 格式123函数名: 函数体 jr ra #ra寄存器中保存着调用指令下一条代码所在的地址
函数调用
- 格式 jal 函数名
函数传参和返回值
代码语言:javascript复制#需求:定义加法函数 并调用获取返回值int sum(int v,int b)
main:
addi $a1,$zero,50
addi $a2,$zero,100
jal add
li $v0,1
move $a0,$v1
syscall
#结束程序
li $v0,10
syscall
add:
add $v1,$a1,$a2
jr $ra
嵌套函数
栈操作
栈空间拉伸和平衡
入栈和出栈
嵌套函数使用栈保护$ra
代码示例
内存空间布局
从mars中可以查看到内存分布起始物理地址
转成图后:
栈的伸缩在mips和x86架构中是由高地址往低地址进行伸缩, 在arm架构中可升序也可降序
内存碎片
在内存动态分配(heap区)过程中容易出现一些小且不连续的空闲内存区域,这些未被使用的内存称作内存碎片
分类:
- 内部碎片:比如数据在内存中采用4个字节对齐的方式进行存储, 比如我们申请一块3个字节的空间用于存储一个数据,但是系统给我们分配了4个字节空间,这时多出来的一个字节的空间就被称之为内部碎片
- 外部碎片:在我们进行内存回收和分配的时候容易出现外部碎片,比如我连续申请了三块4个字节的内存空间,当我释放第二块内存空间然后紧接着申请一块8个字节的空间,此时由于之前释放的4个字节空间太小无法使用,这就造成了内存块空闲,这种碎片叫做外部碎片
PC 寄存器
称作 程序计数寄存器(Program Counter Register) :用于存储程序即将要执行的指令所对应在内存中的实际物理地址, 如果改变该值可以让指令跳转到我们想要跳转的地方
如何修改pc寄存器中的值
使用以下转移指令
jr
指令
jal
指令
j
指令
内存数据的读写
从指定内存中读取数据
从内存中读取数据的宽度取决于寄存器的大小,由于32位cpu寄存器最大存储32位数据,因此lw t0表示一次性读取4个字节的数据到t0寄存器, 如果想要连续读取八个字节的数据,那么需要使用ld t0,表示一次性读取8个字节的数据到t0,
往指定内存中写入数据
代码语言:javascript复制#整型数据
li $s1,4
sw $s1,0x10010000 ;将$s1寄存器中的数据存入0x10010000这个物理地址
#单精度浮点数
.data
f1: .float 3.14
.text
lwc1 $f2,f1
swc1 $f2,0x10010000
#双精度浮点数
.data
d1: .double 3.14
.text
ldc1 $f2,d1
sdc1 $f2,0x10010000
- 第二种 在代码段中使用指令
以上直接使用的是简单粗暴的十六进制表示物理地址,很多时候内存的地址会保存在寄存器中,你可能会看到以下写法:
代码语言:javascript复制lw $s1, $s2
sw $s1, $s2
或者
lw $s1, 20($s2)
sw $s1, 20($s2) ;将地址往高位偏移20个字节 相当于sw $s1, 20 $s2
或者
lw $s1, -20($s2)
sw $s1, -20($s2) ;将地址往低位偏移20个字节
注意: 往指定内存中读取写入数据时,代码段不允许直接写入和读取
一维数组的定义
数组本质上就是多个数据的集合,在内存中按照一定顺序排列,角标即为每个数据的偏移值,在mips中内存数据是按照4个字节进行对齐的,也就是说一个数据最少占用4个字节内存空间,因此数组中数据之间的偏移量固定为n*4
代码语言:javascript复制.data
array: .space 20 #别名的另外一种用法 通过array(寄存器)这种格式 寄存器中存放地址偏移地址量
.text
# $t0寄存器存放角标值 $s1中存放需要存入的值
li $s1,1
li $t0,0
sw $s1,array($t0) #相当于 sw $s1,array $t0
li $s1,2
li $t0,4
sw $s1,array($t0)
li $s1,3
li $t0,8
sw $s1,array($t0)
数组的打印
代码语言:javascript复制.data
array: .space 20
.text
#初始化数组中的数据
li $s1,1
li $t0,0
sw $s1,array($t0)
li $s1,2
li $t0,4
sw $s1,array($t0)
li $s1,3
li $t0,8
sw $s1,array($t0)
#查找角标为2的数值
getData:
la $s1 ,array
li $a0,2
mul $a0,$a0,4
add $s1,$s1,$a0
lw $a0,0($s1)
li $v0,1
syscall
#将角标临时置为0 方便下面循环操作
li $t0,0
while:
beq $t0,12,exit
lw $t2,array($t0)
addi $t0,$t0,4
li $v0,1
move $a0,$t2
syscall
j while
exit:
li $v0,10
syscall
快速初始化数组数据的方法
代码语言:javascript复制.data
array: .word 20 :3 #批量定义3个整型数据20
分支跳转指令
- 整型数据分支比较跳转
bgt
(branch if greater than):用于大于比较
bgt $t0,$t1,sub # 如果$t0中的数据大于$t1,则跳转到sub分支,执行sub中的代码,否则,按照顺序执行bgt下面的代码, sub是一个代号,可以自定义
sub:
beq
(branch equal):用于等于比较
beq $t0,$t1,sub # 如果$t0中的数据等于$t1,则跳转到sub分支,执行sub中的代码,否则,按照顺序执行bgt下面的代码, sub是一个代号,可以自定义
sub:
ble
(branch if less than):用于小于比较
ble $t0,$t1,sub # 如果$t0中的数据小于$t1,则跳转到sub分支,执行sub中的代码,否则,按照顺序执行bgt下面的代码, sub是一个代号,可以自定义
sub:
练习1: 将以下c代码转换成mips汇编代码:
代码语言:javascript复制scanf("%d",$a);
scanf("%d",$b);
if(a>b){
printf("YES");
}else{
printf("NO");
}
汇编代码:
代码语言:javascript复制# 用$t0指代a ,$t1指代b
.data
msg_yes: .ascii "YES " # 表示字符串结尾
msg_no: .ascii "NO "
.text
li $v0,5 #控制syscall为读取integer状态
syscall # 此时io控制台显示光标,可输入数字,回车后将输入的数字保存在$v0中
move $t0,$v0 #由于接下来还需要使用$v0 ,为避免数据被覆盖掉 将输入的数据转移到$t0中进行临时保存
li $v0,5
syscall
move $t1,$v0
bgt $t0,$t1,sub
li $v0,4
la $a0,msg_no
syscall
#结束程序
li $v0,10
syscall
sub:
li $v0,4
la $a0,msg_yes
syscall
练习2: 将以下c代码转换成mips汇编代码:
代码语言:javascript复制//求累加之和
//1 2 3 ..... 100
int i=1;
int s=0;
while(i<=100){
s=s i;
i=i 1;
}
printf("%d",s);
汇编代码:
代码语言:javascript复制# 用$t0指代i ,$t1指代s
.text
li $t0 ,1
li $t1 ,0
loop:
# s=s i;
add $t1,$t1,$t0
add $t0,$t0,1
ble $t0,100,loop
move $a0,$t1
li $v0,1
syscall
- 浮点型数据分支比较
小于
等于
小于等于
以上是单精度浮点数据的比较示例,如果是双精度,只需将结尾.s
改成.d
即可
mips多文件开发
在文件A中定义函数
代码语言:javascript复制fun:
li $v0,1
li $a0,1
syscall
jr $ra
在文件B中使用关键字.include
引用A文件中的函数
.text
jal fun
.include "A.asm"
所有文件必须在同一目录下
宏
- 宏替换
全局替换,使用我们之前学过的
.include
伪指令进行替换 - 宏匹配
在汇编中,如果我们要依次打印1,2,3三个整数,那么汇编如下:
代码语言:javascript复制print1:
li $v0,1
li $a0,1
syscall
jr $ra
print2:
li $v0,1
li $a0,2
syscall
jr $ra
print2:
li $v0,1
li $a0,3
syscall
jr $ra
我们发现使用标签的方式定义函数,当函数体内容存在不确定变量值时,代码非常冗余, 如果使用高级语言进行封装的话,我们一般一个函数就搞定了:
代码语言:javascript复制void print(int a){
print(a);
}
有没有办法使得汇编能像高级语言一样简洁呢?
在MARS中给我们提供了一个扩展伪指令,叫做宏匹配
宏匹配使用的格式如下:
代码语言:javascript复制.macro 别名
#汇编指令...
.end_macro
示例:
代码语言:javascript复制li $v0,10
syscall
#比如我们要对以上两行指令使用宏匹配进行封装
#封装结果为
.macro exit
li $v0,10
syscall
.end_macro
#在代码中引用
.text
exit #直接使用别名调用
如果我们要封装一个打印整型数据的函数,那么我们可以:
代码语言:javascript复制#封装结果为
.macro print_int(%param)
li $v0,1
li $a0,%param
syscall
.end_macro
#在代码中引用
.text
print_int(1) #直接使用别名调用
print_int(2)
print_int(3)
这样是不是和高级语言没什么区别啦
打印字符串封装示例:
代码语言:javascript复制.macro print_str (%str)
.data
myLabel: .asciiz %str
.text
li $v0, 4
la $a0, myLabel
syscall
.end_macro
print_str ("test1")
print_str ("test1")
然后结合我们之前学过的多文件开发,完全可以将这个封装好的函数单独放在一个文件中,直接在头部.include
就行
宏定义
代码语言:javascript复制.eqv LIMIT 20 #给20这个立即数取个别名为LIMIT
.eqv CTR $t2
.eqv CLEAR_CTR add CTR, $zero, 0
代码语言:javascript复制.text
li $v0,1
add $t2, $zero, 0
li $t0,20
代码语言:javascript复制.eqv LIMIT 20 #给20这个立即数取个别名为LIMIT
.eqv CTR $t2
.eqv CLEAR_CTR add CTR, $zero, 0
.text
li $v0,1
CLEAR_CTR
li $t0,LIMIT
注:宏定义和宏匹配必须先定义后使用,也就是说定义的代码需要放在前头
二维数组的定义
二维数组其实就类似于我们数学中的二维坐标系,我们如果要定位一个点的话可以使用(x,y)来表示,在计算机的世界里,二维中所有的点都按照顺序依次存放在内存当中
假设我们将第一维当做行,第二维当做列,那么排列的方式有以下两种:
第一种是 行不动,列轮动
代码语言:javascript复制arr[2][2]
这种方式获取实际地址的公式为:
代码语言:javascript复制addr=baseAddr (rowIndex*colSize colIndex)*dataSize
实际地址=首地址 (第几行*总列数 第几列)*数据占用的宽度
比如:我要计算arr[2][1]的实际物理地址, 那么
实际地址=0x00000000 (2*3 1)*4=0x00000000 0x0000001C=0x0000001C
第二种是 列不动,行轮动
代码语言:javascript复制arr[2][2]
这种方式获取实际地址的公式为:
代码语言:javascript复制addr=baseAddr (colIndex*rowSize rowIndex)*dataSize
实际地址=首地址 (第几列*行数 第几行)*数据占用的宽度
比如:我要计算arr[2][1]的实际物理地址, 那么
实际地址=0x00000000 (1*3 2)*4=0x00000000 0x00000014=0x00000014
使用mips汇编实现二维数组定义
代码语言:javascript复制#需求:实现int a[3][3] = {{1, 2, 3}, {5, 6, 7}, { 10, 11, 12}};
#以下是以 行不动 列轮动的方式实现
.data
da: .word 1,2,3,5,6,7,10,11,12
array: .space 36
.text
# void initArry(*arr,row,col,index)
initArry:
#首地址
la $a0,array
#第几行
li $a1,0
#第几列
li $a2,0
#存第几个数
li $a3,0
while:
# arr[x][y]
bgt $a1,2,exit
# 避免寄存器中的参数被子函数覆盖 将数据放置在栈中临时保存
add $sp,$sp,-12
sw $a1,8($sp)
sw $a2,4($sp)
sw $a3,0($sp)
jal saveDataToMemory
#从栈中恢复局部变量值
lw $a1,8($sp)
lw $a2,4($sp)
lw $a3,0($sp)
add $sp,$sp,12
#累加计数
add $a3,$a3,4
#列轮动
addi $a2,$a2,1
bgt $a2,2,sub
j while
sub:
li $a2,0
add $a1,$a1,1
j while
saveDataToMemory:
# 避免寄存器中的参数被子函数覆盖 将数据放置在栈中临时保存
add $sp,$sp,-4
sw $ra,0($sp)
#计算数据存放的物理地址
jal getAddr
#获取需要存放的数据
lw $t0,da($a3)
#将数据存入指定位置中
sw $t0,0($v0)
lw $ra,0($sp)
add $sp,$sp,4
jr $ra
#算法部分
getAddr:
#实际地址=首地址 (第几行*总列数 第几列)*数据占用的宽度
mul $a1,$a1,3
add $a2,$a2,$a1
mul $a2,$a2,4
add $v0,$a2,$a0
jr $ra
exit:
#程序结束之前测试数据能否正常取出
jal getDataFromArry
li $v0,10
syscall
#获取指定坐标位置的数据arr[2][1] 输出值为11
getDataFromArry:
#第几行
li $a1,2
#第几列
li $a2,1
#计算物理地址
jal getAddr
#获取数据
lw $t0,0($v0)
#打印数据
move $a0,$t0
li $v0,1
syscall
列不不动 行轮动方式:
代码语言:javascript复制#需求:实现int a[3][3] = {{1, 2, 3}, {5, 6, 7}, { 10, 11, 12}};
#以下是以 列不动,行轮动的方式实现
.data
da: .word 1,2,3,5,6,7,10,11,12
array: .space 36
.text
# void initArry(*arr,row,col,index)
initArry:
#首地址
la $a0,array
#第几行
li $a1,0
#第几列
li $a2,0
#存第几个数
li $a3,0
while:
# arr[x][y]
bgt $a2,2,exit
# 避免寄存器中的参数被子函数覆盖 将数据放置在栈中临时保存
add $sp,$sp,-12
sw $a1,8($sp)
sw $a2,4($sp)
sw $a3,0($sp)
jal saveDataToMemory
#从栈中恢复局部变量值
lw $a1,8($sp)
lw $a2,4($sp)
lw $a3,0($sp)
add $sp,$sp,12
#累加计数
add $a3,$a3,4
#行轮动
addi $a1,$a1,1
bgt $a1,2,sub
j while
sub:
li $a1,0
add $a2,$a2,1
j while
saveDataToMemory:
# 避免寄存器中的参数被子函数覆盖 将数据放置在栈中临时保存
add $sp,$sp,-4
sw $ra,0($sp)
#计算数据存放的物理地址
jal getAddr
#获取需要存放的数据
lw $t0,da($a3)
#将数据存入指定位置中
sw $t0,0($v0)
lw $ra,0($sp)
add $sp,$sp,4
jr $ra
#算法部分
getAddr:
#实际地址=首地址 (第几列*行数 第几行)*数据占用的宽度
mul $a2,$a2,3
add $a1,$a1,$a2
mul $a1,$a1,4
add $v0,$a1,$a0
jr $ra
exit:
#程序结束之前测试数据能否正常取出
jal getDataFromArry
li $v0,10
syscall
#获取指定坐标位置的数据arr[2][1] 输出7
getDataFromArry:
#第几行
li $a1,2
#第几列
li $a2,1
#计算物理地址
jal getAddr
#获取数据
lw $t0,0($v0)
#打印数据
move $a0,$t0
li $v0,1
syscall
更为简便的方法实现二维数组的搭建
由于数组中数据是在内存中连续进行排列存储的,那么我们可以之间将数据 依次存入内存之中,然后使用算法进行数据获取即可(以下示例皆采用 行不动,列动 的方式)
代码语言:javascript复制#需求:实现int a[3][3] = {{1, 2, 3}, {5, 6, 7}, { 10, 11, 12}};
#由于数组中的数据是存放在堆内存中,需要在程序执行时动态分配
.text
#堆地址从0x10040000开始
la $a0,0x10040000
#将数据按照顺序存放至内存中
jal initData
#获取数据
jal getDataFromArry
j exit
initData:
li $s1,1
sw $s1,0($a0)
li $s1,2
sw $s1,4($a0)
li $s1,3
sw $s1,8($a0)
li $s1,5
sw $s1,12($a0)
li $s1,6
sw $s1,16($a0)
li $s1,7
sw $s1,20($a0)
li $s1,10
sw $s1,24($a0)
li $s1,11
sw $s1,28($a0)
li $s1,12
sw $s1,32($a0)
jr $ra
#算法部分
getAddr:
#实际地址=首地址 (第几行*总列数 第几列)*数据占用的宽度
mul $a1,$a1,3
add $a2,$a2,$a1
mul $a2,$a2,4
add $v0,$a2,$a0
jr $ra
exit:
#程序退出
li $v0,10
syscall
#获取指定坐标位置的数据arr[2][1] 输出值为11
getDataFromArry:
#第几行
li $a1,2
#第几列
li $a2,1
#计算物理地址
jal getAddr
#获取数据
lw $t0,0($v0)
#打印数据
move $a0,$t0
li $v0,1
syscall
再简化一下:
代码语言:javascript复制#需求:实现int a[3][3] = {{1, 2, 3}, {5, 6, 7}, { 10, 11, 12}};
.data
da: .word 1,2,3,5,6,7,10,11,12
# 假如以上数据是动态写入的 当做数组中的数据来用
.text
#那么只需要提供首地址然后配合算法就能获取到指定坐标的数据
la $a0,da
jal getDataFromArry
j exit
#算法部分
getAddr:
#实际地址=首地址 (第几行*总列数 第几列)*数据占用的宽度
mul $a1,$a1,3
add $a2,$a2,$a1
mul $a2,$a2,4
add $v0,$a2,$a0
jr $ra
exit:
#程序退出
li $v0,10
syscall
#获取指定坐标位置的数据arr[2][1] 输出值为11
getDataFromArry:
#第几行
li $a1,2
#第几列
li $a2,1
#计算物理地址
jal getAddr
#获取数据
lw $t0,0($v0)
#打印数据
move $a0,$t0
li $v0,1
syscall
按照正常的编程思维,我们一般使用第一种行不动 列动的存储方式 第一维为行,第二维为列,如果你想改成行动存储方式,有两种方法:要么将数据的存储顺序进行变动,配合第二种算法,要么将第二维当成行,第一维当成列,配合第二种算法进行处理
Mips汇编指令汇总表
类别 | 指令名称 | 实例 | 含义 | 注释 | 英文注解 |
---|---|---|---|---|---|
算数 | 加法 | add $s1, $s2, $s3 | $s1 = $s2 $s3 | 三个寄存器操作数 | addition 加法 |
减法 | sub $s1, $s2, $s3 | $s1 = $s2 - $s3 | 三个寄存器操作数 | subtraction 减法 | |
立即数加法 | addi $s1, $s2, 20 | $s1 = $s2 20 | 用于加常数数据 | add immediate 立即加法 | |
数据传输 | 取字 | lw $s1, 20 ($s2) | $s1 = Memory[$s2 20] | 将一个字从内存中取到寄存器中 | load word 加载字 |
存字 | sw $s1, 20 ($s2) | Memory[$s2 20] = $s1 | 将一个字从寄存器中取到内存中 | store word 存储字 | |
取半字 | lh $s1, 20 ($s2) | $s1 = Memory[$s2 20] | 将半个字从内存中取到寄存器中 | load halfword 加载半字 | |
取无符号半字 | lhu $s1, 20 ($s2) | $s1 = Memory[$s2 20] | 将半个字从内存中取到寄存器中 | load halfword unsigned | |
存半字 | sh $s1, 20 ($s2) | Memory[$s2 20] = $s1 | 将半个字从寄存器中取到内存中 | stroe halfword 存储半字 | |
取字节 | lb $s1, 20 ($s2) | $s1 = Memory[$s2 20] | 将一字节从内存中取到寄存器中 | load byte | |
取无符号字节 | lbu $s1, 20 ($s2) | $s1 = Memory[$s2 20] | 将一字节从内存中取到寄存器中 | load byte unsigned | |
存字节 | sb $s1, 20 ($s2) | Memory[$s2 20] = $s1 | 将一字节从寄存器中取到内存中 | store byte | |
取链接字 | ll $s1, 20 ($s2) | $s1 = Memory[$s2 20] | 取字作为原子交换的前半部 | load linked | |
存条件字 | sc $s1, 20 ($s2) | Memory[$s2 20] = $s1;$s1 = 0 or 1 | 存字作为原子交换的后半部分 | store conditional | |
取立即数的高位 | lui $s1, 20 | $s1 = 20 * 216 | 取立即数并放到高16位 | load upper immediate | |
逻辑 | 与 | and $s1, $s2, $s3 | $s1 = $s2 & $s3 | 三个寄存器操作数按位与 | and |
或 | or $s1, $s2, $s3 | $s1 = $s2 | $s3 | 三个寄存器操作数按位或 | or | |
或非 | nor $s1, $s2, $s3 | $s1 = ~ ($s2 | $s3) | 三个寄存器操作数按位或非 | not or | |
立即数与 | andi $s1, $s2, 20 | $s1 = $s2 & 20 | 和常数按位与 | and immediate | |
立即数或 | ori $s1, $s2, 20 | $s1 = $s2 | 20 | 和常数按位或 | or immediate | |
逻辑左移 | sll $s1, $s2, 10 | $s1 = $s2 << 20 | 根据常数左移相应位 | set left logical | |
逻辑右移 | srl $s1, $s2, 10 | $s1 = $s2 >> 20 | 根据常数右移相应位 | set right logical | |
条件分支 | 相等时跳转 | beq $s1, $s2, 25 | if ($s1 == $s2) go to PC 4 25 * 4 | 相等检测:和PC相关的跳转 | branch on equal |
不相等时跳转 | bne $s1, $s2, 25 | if ($s1 != $s2) go to PC 4 25 * 4 | 不相等检测:和PC相关的跳转 | branch on not equal | |
小于时跳转 | slt $1, $s2, $3 | if ($s2 < $s3) $s1 = 1; else $s1 = 0 | 比较是否小于 | set less than | |
无符号数比较小时置位 | sltu $1, $s2, $3 | if ($s2 < $s3) $s1 = 1; else $s1 = 0 | 比较是否小于无符号数 | set less than unsigned | |
无符号数小于立即数时置位 | slti $1, $s2, 20 | if ($s2 < 20) $s1 = 1; else $s1 = 0 | 比较是否小于常数 | set less than immediate | |
无符号数比较小于无符号立即数时置位 | sltiu $1, $s2, 20 | if ($s2 < 20) $s1 = 1; else $s1 = 0 | 比较是否小于无符号常数 | set less than immediate unsigned | |
无条件跳转 | 跳转 | j 2500 | go to 2500 * 4 | 跳转到目标地址 | jump |
跳转至寄存器所指位置 | jr $ra | go to $ra | 用于switch语句,以及过程调用 | jump register | |
跳转并链接 | jal 2500 | $ra = PC 4;go to 2500 * 4; | 用于过程调用(方法)正常的执行流程执行完A自然要执行B指令,现在要跳转执行C方法,这时就把B地址存入寄存器中,执行完C后跳转到B | jump and link |
Mips内存结构图: