变量的声明与定义

使用的是伪指令,这些伪指令主要用途是标识数据段和代码段的位置,并为声明的数据分配空间。

1
2
3
4
5
6
.data
fibs: .space 48 # "array" of 12 words to contain fib values
size: .word 12 # size of "array"
space:.asciiz " " # space to insert between numbers
head: .asciiz "The Fibonacci numbers are:\n"
.text

Help文档MIPS->Directives一栏,对所有的伪指令都做了简要的介绍

1) .data

格式:.data [address]

  • 定义程序的数据段,初始地址为address,若无address参数,初始地址为设置的默认地址。
  • 需要用伪指令声明的程序变量需要紧跟着该指令。比如该程序中的fibs, size, space, head这四个变量。

2) .text

格式:.text [address]

  • 定义程序的代码段,初始地址为address,若无address参数,初始地址为设置的默认地址。
  • 该指令后面就是程序代码。
  • 在MARS中如果前面没有使用.data伪指令,可以不使用.text直接编写程序代码,代码将放置在前面设置的代码段默认地址中,但如果前面使用了.data伪指令,务必在代码段开始前使用.text进行标注。

3) .space

格式:[name]: .space [n]

说明:

  • 申请n个字节未初始化的内存空间,类似于其他语言中的数组声明。
  • 这段数据的初始地址保存在标签name中。
  • name的地址是由.data段的初始地址加上前面所申请的数据大小计算得出的。由于前面申请的空间大小不定,有可能会出现后来申请的空间没有字对齐的情况,从而在使用sw,lw一类指令时出现错误,所以在申请空间时尽可能让n为4的倍数,防止在数据存取过程中出现问题。
  • 在本例中,事先申请了48个字节也就是12个字的内存空间,用来保存我们之后计算出来的12个Fibonacci数,地址标签为fibs。

4) .word

格式:[name]: .word [data1],[data2] ….

  • 在内存数据段中以字为单位连续存储数据data1,data2,… (也就是将datax写入对应的1个字的空间,注意.word和.space的区别)
  • 这段数据的初始地址保存在标签name中。计算方式与上面相同。
  • 在本例中,把需要计算的Fibonacci数的个数保存在了内存数据段,地址标签为size。

3) .asciiz

格式:[name]: . asciiz “[content]”

  • 以字节为单位存储字符串,末尾以NULL结尾。
    这个字符串在内存数据区的初始地址保存在标签name中。
    注意.asciiz.ascii这两条伪指令的区别。.ascii,字符串的末尾没有结束符’\0’
  • .asciiz由于是按字节存储,可能会导致之后分配的空间首地址无法字对齐的情况发生
  • 本例中,声明了两个字符串,一个是输出时需要用到的空格符,一个是输出语句,地址标签分别为space和head。

可实现代码的复用。宏分为两种,不带参数的宏和带参数的宏。

无参数

不带参数的宏,定义的方式如下:

1
2
3
.macro macro_name
# 代码段
.end_macro

例如:

1
2
3
4
.macro done
li $v0,10
syscall
.end_macro

此时,在需要程序停止运行的地方,使用done语句,就可以让程序在那里退出。汇编器会把所有的macro_name语句替换成代码段;

带参数

1
2
3
4
5
.macro	getindex(%ans,%i,%j)
sll %ans,%i,3
add %ans,%ans,%j
sll %ans,%ans,2
.end_macro

%i代表行数,%j代表列数,%ans就是计算出来的结果((%i*8+%j)*4)。使用getindex($t2,$t0,$t1)来调用这个宏,汇编器会用这段代码替换它,同时%ans被替换成$t2,%i被替换成$t0,%j被替换成$t1

.eqv

1
.eqv EQV_NAME string

汇编器会把所有EQV_NAME的地方替换成string,这可以用来定义一些常量。

分配寄存器

循环运算

系统调用

image

1. 字符串输出

1
2
3
la $a0,addr
li $v0,4
syscall
  • 首先把要输出的字符串在内存中的首地址赋给$a0寄存器,然后汇编器就会根据$a0中的地址将字符串输出。
  • 在内存中存储的字符串是以NULL(‘\0’)作为结束符,输出时遇到这个结束符就会停止。

2. 整数输出

1
2
li $v0,1
syscall
  • 这个系统调用的功能就是把$a0寄存器中的数据以整数的形式输出。

3. 结束程序

1
2
li $v0,10
syscall

注意
$v0 = 8时

1
$a0 = address of input buffer $a1 = maximum number of characters to read

则最多读取字符是$a1-1

条件语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1  .text
2 li $t1, 100 #t1=100
3 li $t2, 200 #t2=200
4 slt $t3, $t1, $t2 #if(t1<t2) t3=1
5 beq $t3, $0, if_1_else
7 nop
8 #do something
9 j if_1_end #jump to end
10 nop
11 if_1_else:
12 #do something else
13 if_1_end:
14 li $v0, 10
15 syscall

第5行判断是否跳转决定了到底是执行do something还是执行do something else。如果判断条件($t1<$t2)为真,即$t3==1,则不会跳转,继续执行do something,否则$t3==0,会跳转到if_1_else继续执行do something else

循环语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1  .text
2 li $t1, 100 #n = 100
3 li $t2, 0 #i
4 for_begin1: #for(int i=0;i<n;i++)
5 slt $t3, $t2, $t1 #{
6 beq $t3, $0, for_end1
7 nop
8 #do something
9 addi $t2, $t2, 1 #i++
10 j for_begin1
11 nop #}
12 for_end1:
13 li $v0, 10
14 syscall

对任何一个跳传指令后面,要加上一个空转指令(NOP)。从而使得CPU的PIPELINE不会错误的执行一个预取(PRE_FETCH)得指令。当然这个NOP可以替换为别的,放一个NOP是最简单和安全的