汇编语言之MIPS汇编

2021-08-24 15:09:54 浏览数 (1)

简介

咱们知道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 #代码段

传送指令

  1. 加载立即数指令 li

li(load immediate) :用于将立即数传送给寄存器

代码语言:javascript复制
li $t0,1  ;十六进制数据使用0x前缀表示
  1. 加载地址指令 la

la(load address) :用于将地址传送至寄存器中, 多用于通过地址获取数据段中的地址

代码语言:javascript复制
.data 
msg: .ascii "hello world"

.text
la $a0,msg # 将字符串数据所在的地址赋值给$a0寄存器
  1. 寄存器数据传送指令move

用于将一个寄存器中的数据传送至另一个寄存器当中

代码语言:javascript复制
move $t0,$t1  # 将寄存器$t1中的数据传送至$t0

系统服务指令 syscall

在C语言中输出文本可以使用printf函数,但是汇编中没有printf这么一说,如果想要输出文本,需要借助syscall指令

如果想要输出一个数字1,那么syscall指令从$a0寄存器中取出需要输出的数据

因此, 你在执行syscall指令之前需要将数据提前放入$a0之中:

代码语言:javascript复制
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

分支跳转指令

  1. 整型数据分支比较跳转
  • bgt(branch if greater than):用于大于比较
代码语言:javascript复制
bgt $t0,$t1,sub # 如果$t0中的数据大于$t1,则跳转到sub分支,执行sub中的代码,否则,按照顺序执行bgt下面的代码, sub是一个代号,可以自定义


sub:
  • beq(branch equal):用于等于比较
代码语言:javascript复制
beq $t0,$t1,sub # 如果$t0中的数据等于$t1,则跳转到sub分支,执行sub中的代码,否则,按照顺序执行bgt下面的代码, sub是一个代号,可以自定义


sub:
  • ble(branch if less than):用于小于比较
代码语言:javascript复制
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
  1. 浮点型数据分支比较

小于

等于

小于等于

以上是单精度浮点数据的比较示例,如果是双精度,只需将结尾.s改成.d即可

mips多文件开发

在文件A中定义函数

代码语言:javascript复制
fun:
  li $v0,1
  li $a0,1
  syscall 
  jr $ra

在文件B中使用关键字.include引用A文件中的函数

代码语言:javascript复制
.text
jal fun

.include "A.asm"

所有文件必须在同一目录下

  1. 宏替换 全局替换,使用我们之前学过的.include伪指令进行替换
  2. 宏匹配

在汇编中,如果我们要依次打印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内存结构图:

0 人点赞