MIPS: relevant use for a stack pointer ($sp) and the stack
目前,我正在为我的计算机组织中期学习,并且试图完全了解堆栈指针和堆栈。 我知道围绕这个概念的以下事实:
- 遵循先进先出的原则
-
向堆栈中添加内容需要两个步骤:
1
2addi $sp, $sp, -4
sw $s0, 0($sp)
我认为让我无法完全理解的是,我无法提出一种相关的,不言而喻的情况,在这种情况下,我将需要和/或想要使用堆栈指针来跟踪数据。
有人可以详细介绍整个概念,并给我一些有用的代码示例吗?
堆栈的一个重要用途是嵌套子程序调用。
每个子例程可以具有该子例程本地的一组变量。这些变量可以方便地存储在堆栈框架中的堆栈中。一些调用约定也会在堆栈上传递参数。
使用子例程还意味着您必须跟踪调用者,即返回地址。
一些体系结构为此目的有专用的堆栈,而另一些体系结构隐式地使用"普通"堆栈。默认情况下,MIPS仅使用寄存器,但是在非叶函数(即调用其他函数的函数)中,返回地址将被覆盖。因此,您必须保存原始值,通常将其保存在局部变量中的堆栈上。调用约定还可以声明某些寄存器值必须在函数调用之间保留,您可以类似地使用堆栈来保存和恢复它们。
假设您有以下C片段:
1 2 3 4 5 6 7 8 | extern void foo(); extern int bar(); int baz() { int x = bar(); foo(); return x; } |
MIPS程序集可能如下所示:
1 2 3 4 5 6 7 8 9 | addiu $sp, $sp, -8 # allocate 2 words on the stack sw $ra, 4($sp) # save $ra in the upper one jal bar # this overwrites $ra sw $v0, ($sp) # save returned value (x) jal foo # this overwrites $ra and possibly $v0 lw $v0, ($sp) # reload x so we can return it lw $ra, 4($sp) # reload $ra so we can return to caller addiu $sp, $sp, 8 # restore $sp, freeing the allocated space jr $ra # return |
MIPS调用约定要求前四个功能参数位于寄存器
因此,如果要访问参数5(以及其他参数),则需要使用
如果您的函数具有局部变量,并且无法将所有变量都保留在寄存器中(例如当调用其他函数时不能保留
例如,如果您有:
1 2 3 4 | int tst5(int x1, int x2, int x3, int x4, int x5) { return x1 + x2 + x3 + x4 + x5; } |
它的拆卸类似于:
1 2 3 4 5 6 7 | tst5: lw $2,16($sp) # r2 = x5; 4 slots are skipped addu $4,$4,$5 # x1 += x2 addu $4,$4,$6 # x1 += x3 addu $4,$4,$7 # x1 += x4 j $31 # return addu $2,$4,$2 # r2 += x1 |
请参见,
然后,如果您有类似以下的代码:
1 2 3 4 5 6 7 8 9 | int binary(int a, int b) { return a + b; } void stk(void) { binary(binary(binary(1, 2), binary(3, 4)), binary(binary(5, 6), binary(7, 8))); } |
这是编译后反汇编的样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | binary: j $31 # return addu $2,$4,$5 # r2 = a + b stk: subu $sp,$sp,32 # allocate space for local vars & 4 slots li $4,0x00000001 # 1 li $5,0x00000002 # 2 sw $31,24($sp) # store return address on stack sw $17,20($sp) # preserve r17 on stack jal binary # call binary(1,2) sw $16,16($sp) # preserve r16 on stack li $4,0x00000003 # 3 li $5,0x00000004 # 4 jal binary # call binary(3,4) move $16,$2 # r16 = binary(1,2) move $4,$16 # r4 = binary(1,2) jal binary # call binary(binary(1,2), binary(3,4)) move $5,$2 # r5 = binary(3,4) li $4,0x00000005 # 5 li $5,0x00000006 # 6 jal binary # call binary(5,6) move $17,$2 # r17 = binary(binary(1,2), binary(3,4)) li $4,0x00000007 # 7 li $5,0x00000008 # 8 jal binary # call binary(7,8) move $16,$2 # r16 = binary(5,6) move $4,$16 # r4 = binary(5,6) jal binary # call binary(binary(5,6), binary(7,8)) move $5,$2 # r5 = binary(7,8) move $4,$17 # r4 = binary(binary(1,2), binary(3,4)) jal binary # call binary(binary(binary(1,2), binary(3,4)), binary(binary(5,6), binary(7,8))) move $5,$2 # r5 = binary(binary(5,6), binary(7,8)) lw $31,24($sp) # restore return address from stack lw $17,20($sp) # restore r17 from stack lw $16,16($sp) # restore r16 from stack addu $sp,$sp,32 # remove local vars and 4 slots j $31 # return nop |
希望我能对代码进行注释而不会出错。
因此,请注意,编译器选择在函数中使用
PS请记住,在将控制权实际转移到新位置之前,MIPS上的所有分支/跳转指令均有效执行紧随其后的指令。这可能会造成混淆。