【问题标题】:MIPS - How does MIPS allocate memory for arrays in the stack?MIPS - MIPS 如何为堆栈中的数组分配内存?
【发布时间】:2013-11-05 21:45:49
【问题描述】:

我对 MIPS 汇编语言很陌生,目前正在学习计算机体系结构课程,其中有很大一部分是关于 MIPS 编码的。过去我研究过其他几种高级编程语言(C、C#、Python),因此有一些编程基础。

我的问题在这里特别问:MIPS 如何为堆栈中的数组分配内存?我希望回答这个问题能让我对 MIPS 有更好的全面理解,因为我对 MIPS 语言及其架构的概念还有很多了解。我也不太明白指针在这方面是如何工作的......

如果有人能花时间帮助这个困惑的学生,那就太好了! :)

【问题讨论】:

  • 感谢汉斯!

标签: assembly malloc mips computer-architecture


【解决方案1】:

嗯.. 你应该知道 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 

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

#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 模拟器中,这是 system call 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 没有推送或弹出寄存器/立即指令。所以你依靠自己管理堆栈。这实际上在 mul/div 之外的大多数拱门中都有说明,您的寄存器没有特定用途,只是建议的使用方式。现在,如果您随心所欲地使用它,例如,如果您尝试与 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
    

    这是一个使用两个词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 没有推送/弹出功能。 (ARM 不完全是 RISC,它的 push/pop(存储-倍数/加载-倍数)是 ISA 设计的主要示例,它偏离了 RISC 理念以获得代码密度)。
    • sw 0($sp), 0($t0) 存储指令必须有一个寄存器源,例如sw $t1, 0($t0)You can't do this even on x86。我查了一下,连MARS都不把它当作伪指令用LW转成$at给你,所以你需要自己lw。除非您真的对自己的架构知识/经验充满信心,否则请确保您的代码至少可以编译/组装,即使您没有测试运行它(因为您只展示了不能单独工作的片段)。
    • @PeterCordes 我以为我解决了这个问题。我这样做是为了我自己的参考。
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 2013-02-02
      • 1970-01-01
      • 2017-02-04
      • 1970-01-01
      • 1970-01-01
      • 2015-02-04
      • 1970-01-01
      • 2018-09-17
      • 2019-09-27
      相关资源
      最近更新 更多