MIPS - MIPS如何在堆栈中为数组分配内存?

15

我对MIPS汇编语言还比较陌生,目前正在修读一门计算机体系结构课程,其中有很大一部分是关于MIPS编码的。我以前学过几种高级编程语言(C、C#、Python),因此对编程有一定的基础。

我的问题是:MIPS如何在堆栈中为数组分配内存?希望回答这个问题能够让我更好地理解MIPS,因为我仍然有些概念混乱,不太明白MIPS语言及其架构的整体思想。我也不太理解指针在这方面是如何工作的......

如果有人能抽出时间来帮助这位困惑的学生,那就太棒了!:)

3个回答

27

嗯...你应该知道MIPS和C一样,基本上有三种不同的内存分配方式。

考虑下面的C代码:

int arr[2]; //global variable, allocated in the data segment

int main() {
    int arr2[2]; //local variable, allocated on the stack
    int *arr3 = malloc(sizeof(int) * 2); //local variable, allocated on the heap
}

MIPS汇编支持所有这些类型的数据。

要在数据段中分配一个int数组,可以使用:

.data

arr: .word 0, 0 #enough space for two words, initialized to 0, arr label points to the first element 

要在堆栈上分配一个整数数组,您可以使用:

#save $ra
addi $sp $sp -4  #give 4 bytes to the stack to store the frame pointer
sw   $fp 0($sp)  #store the old frame pointer
move $fp $sp     #exchange the frame and stack pointers
addi $sp $sp -12 #allocate 12 more bytes of storage, 4 for $ra and 8 for our array
sw   $ra  -4($fp)

# at this point we have allocated space for our array at the address -8($fp)

为了在堆上分配空间,需要进行系统调用。在spim模拟器中,这是系统调用9

li $a0 8 #enough space for two integers
li $v0 9 #syscall 9 (sbrk)
syscall
# address of the allocated space is now in $v0

2
MIPS与其他架构不同,没有推送或弹出寄存器/立即指令。因此,您需要自己管理堆栈。在除了乘法/除法以外的大多数情况下,这实际上在大多数架构中都有所提及,其中您的寄存器没有特定用途,只是建议使用的方式。现在,如果您想随意使用它,例如尝试与C集成,那么您可能会破坏一些东西。
为了将某些内容推送到堆栈中,您需要使用存储指令。这些指令包括:sb、sh、sw、swl和swr。分别表示字节、半字、字、左侧字和右侧字。
addiu $sp, $sp, -4   # push stack 1 word
sw $t0, 0($sp)       # place item on newly pushed space

为了从栈中弹出某个元素,你只需要使用addiu进行减一操作。然而,你可能想要使用lb、lh、lw、lwl、lwr等指令来加载数据。
lw $t0, 0($sp)
addiu $sp, $sp, 4   # pop stack 1 word

这是一个使用“two word push”功能的示例。
addiu $sp, $sp, -8  # allocate two words
sw $t0, 0($sp)      # push two registers t0 t1
sw $t1, 4($sp)

lw $t1, 4($sp)      # pop two registers t0 t1
lw $t0, 0($sp)
addiu $sp, $sp, 8   # deallocate two words

这是一个使用它作为返回地址的示例,以便非叶子函数的调用不会使您出错。
# grab us a quick string
.data
example_str: .asciiz "hello world :^)"

# grab us a function
.text
    .globl example
    .type test, @function
test:
    addiu $sp, $sp, -4  # push stack for 1 word
    sw $ra, 0($sp)      # save return address
    
    la $a0, example_str # call puts and give it a string
    jal puts
    nop
    
    lw $ra, 0($sp)      # load return address
    addiu $sp, $sp, 4   # pop stack for 1 word
    
    jr $ra              # return from function to caller
    nop

这是将多个元素推入一行的示例。弹出当然是相反的操作。
.data
example_arr: .word 0, 0, 0, 0

.text
addiu $sp, $sp, -16
la $t0, example_arr
lw $t1, 0($t0)
sw $t1, 0($sp)
lw $t1, 0($t0)
sw $t1, 4($sp)
lw $t1, 0($t0)
sw $t1, 8($sp)
sw $t1, 12($sp)

这是使用malloc/calloc的一个例子。
# grab us a function
.text
    .globl example
    .type test, @function
test:
    addiu $sp, $sp, -4  # push stack for 1 word
    sw $ra, 0($sp)      # save return address
    
    li $a0, 4           # allocate 4*4 bytes (16)
    li $a1, 4
    jal calloc
    nop
    
    addiu $sp, $sp, -4  # push stack for 1 word
    sw $v0, 0($sp)      # save calloc'd buffer
    
    move $t0, $v0       # get the buffer into a temp
    li $t1, 1           # fill some temps with numbers
    li $t2, 2
    li $t3, 3
    li $t4, 4
    sw $t1, 0($t0)      # save some temps to buffer
    sw $t2, 4($t0)
    sw $t3, 8($t0)
    sw $t4, 12($t0)
    
    ... do stuff with the buffer ...
    
    lw $a0, 0($sp)      # pop buffer from stack
    jal free            # run it through free
    nop
    addiu $sp, $sp, 4   # don't forget to decrement
    
    lw $ra, 0($sp)      # load return address
    addiu $sp, $sp, 4   # pop stack for 1 word
    
    jr $ra              # return from function to caller
    nop

如��之前提到的,没有硬性定义的特定用途,因此如果您愿意,也可以使用自己的堆栈并忘记使用$sp。我展示了一些例子,其中我将$t*用作$s*。这在强制每个函数拥有自己的堆栈或其他用例时非常有效。例如,Lua(https://lua.org)在某种程度上就是这样做的。然而,在MIPS中不是这样的。当处理多个目标时,多个堆栈非常好用。
编辑: 我意识到我遗漏了堆栈帧指针。如果您的代码与C语言编写的内容链接在一起,请注意正确处理堆栈帧指针。

它不像x86,但对于RISC来说相当典型。PowerPC和大多数其他RISC没有push/pop指令。(ARM并非完全符合RISC,其push/pop(存储多个/加载多个)是ISA设计的主要例子,这些偏离了RISC哲学以获得代码密度)。 - Peter Cordes
sw 0($sp), 0($t0) 这是一个存储指令,必须有一个寄存器作为源,例如 sw $t1, 0($t0)!即使在 x86 上也不能这样做。我查了一下,即使是 MARS 也不会将其视为伪指令,并将 LW 转换成 $at,所以你需要自己使用 lw。除非你对某种体系结构的知识/经验非常自信,否则请确保你的代码至少可以编译/汇编,即使你没有测试运行它(因为你只显示了一个无法独立工作的片段)。 - Peter Cordes
@PeterCordes 我以为我已经修复了那个问题。我只是为了自己的参考而这样做。 - user2262111

0

在MIPS中,我们手动管理堆栈,因此我们使用存储指令“sb sh sw swl swr”


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接