无法弄清楚程序中堆栈的使用?

时间:2011-10-21 20:41:45

标签: stack mips

好的,我的任务是修改此代码以计算大小写元音和小写元音。该程序的目的是演示使用堆栈来保存函数调用之间的数据:

##
## vowel.a - prints out number of vowels in  
##         - the string str
##
##  a0 - points to the string
##

#################################################
#                                               #
#                 text segment                  #
#                                               #
#################################################

            .text           
            .globl __start 
   __start:         # execution starts here


            la $a0,str
            jal vcount      # call vcount

            move $a0,$v0
            li $v0,1
            syscall         # print answer


            la $a0,endl
            li $v0,4
            syscall         # print newline

            li $v0,10
            syscall         # au revoir...

           #------------------------------------------------
           # vowelp - takes a single character as a
           # parameter and returns 1 if the character 
           # is a (lower case) vowel otherwise return 0.
           #        a0 - holds character
           #        v0 - returns 0 or 1
           #------------------------------------------------

           vowelp:  li $v0,0
           beq  $a0,'a',yes
           beq  $a0,'e',yes
           beq  $a0,'i',yes
           beq  $a0,'o',yes
           beq  $a0,'u',yes
           jr $ra
           yes:     li $v0,1
           jr $ra


           #------------------------------------------------
           # vcount - use vowelp to count the vowels in a
           # string.
           #        a0 - holds string address
           #        s0 - holds number of vowels
           #        v0 - returns number of vowels
           #------------------------------------------------

           vcount:  
           sub $sp,$sp,16   # save registers on stack
           sw $a0,0($sp)
           sw $s0,4($sp)
           sw $s1,8($sp)
           sw $ra,12($sp)

           li $s0,0 # count of vowels
           move $s1,$a0     # address of string

           nextc:   lb $a0,($s1)    # get each character
           beqz $a0,done    # zero marks end
           jal vowelp       # call vowelp 
           add $s0,$s0,$v0  # add 0 or 1 to count
           add $s1,$s1,1    # move along string
           b nextc
           done:    move $v0,$s0    # use $v0 for result

           lw $a0,0($sp)    # restore registers
           lw $s0,4($sp)
           lw $s1,8($sp)
           lw $ra,12($sp)
           add $sp,$sp,16
           jr $ra


    #################################################
    #                                               #
    #               data segment                    #
    #                                               #
    #################################################


           .data
     str:   .asciiz "long time ago in a galaxy far away"
     endl:  .asciiz "\n"

     ##
     ## end of file vowel.a

我的修改后的代码有效:

    ##
    ## vowel.a - prints out number of vowels in  
    ##         - the string str
    ##
    ##      a0 - points to the string
    ##

    #################################################
    #                                               #
    #               text segment                    #
    #                                               #
    #################################################

            .text           
            .globl __start 
    __start:                # execution starts here

            la $a0,str
            jal vcount      # call vcount

            move $a0,$v0
            li $v0,1
            syscall         # print answer

            la $a0,endl
            li $v0,4
            syscall         # print newline

            move $a0,$t0
            li $v0,1
            syscall

            la $a0,endl
            li $v0,4
            syscall

            li $v0,10
            syscall         # au revoir...

            vowell: li $v0,0
            beq  $a0,'a',yes
            beq  $a0,'e',yes
            beq  $a0,'i',yes
            beq  $a0,'o',yes
            beq  $a0,'u',yes
            jr $ra
            yes:    li $v0,1
                    jr $ra
            vowelu:
            li $v0,0
            beq $a0,'A',yes
            beq $a0,'E',yes
            beq $a0,'I',yes
            beq $a0,'O',yes
            beq $a0,'U',yes
                    jr $ra

            vcount:
            sub $sp,$sp,20
            sw $a0,0($sp)
            sw $s0,4($sp)
            sw $s1,8($sp)
            sw $ra,12($sp)
            sw $s2,16($sp)

            li $s0,0
            li $s2,0
            move $s1,$a0

            nextc:
            lb $a0,($s1)
            beqz $a0,done
            jal vowell
            add $s0,$s0,$v0
            jal vowelu
            add $s2,$s2,$v0
            add $s1,$s1,1
            b nextc
            done:
            move $v0,$s0
            move $t0,$s2

            lw $a0,0($sp)
            lw $s0,4($sp)
            lw $s1,8($sp)
            lw $ra,12($sp)
            lw $s2,16($sp)
            add $sp,$sp,20
            jr $ra

            .data
            str:    .asciiz "Long Time Ago in a Galaxy Far Far Away"
            endl:   .asciiz "\n"

我不明白最后的lw块是什么。程序将计数分别存储在s0和t0中,那么这一点是什么?它看起来好像只是在最后恢复原始值。哎呀,那只是为了证明它可能吗?

3 个答案:

答案 0 :(得分:1)

我对MIPS知之甚少,但我认为这个想法类似于x86。

正如你所说,最后一个LW正在恢复原始值。发生这种情况的原因是你可以在另一个函数内部调用一个函数(初始样式),而不用担心丢失放在堆栈上的未分配给内存的变量和值(比如迭代器等)。

例如,假设您正在迭代整页文本,一次一行。您将外部循环的迭代器(页面行)存储在寄存器中。现在,当您输入计算元音数量的函数时,您不必担心会丢失该值,因此会将其推入堆栈。你的元音计数器将运行它应该做的事情,使用它想要的任何寄存器,然后当它完成时它将(根据你的方法)将值从栈中恢复到它们原来的位置。这样内部函数就不会破坏外部函数,并且你的寄存器没有被被调用方法的函数粉碎。

答案 1 :(得分:0)

我不熟悉MIPS程序集,但通常每个平台都有关于子程序应该如何表现的约定。其中一个约定通常是CPU注册子程序必须保留的。这些惯例合在一起构成了ABI。

以这种方式思考:当你有一个只有几个子程序的程序时,它很容易跟踪“是的,这个例程会在你每次调用它时都会破坏寄存器X”。但随着你的计划的发展,这变得非常困难。想象一下,更改函数以使用新寄存器的难度 - 您必须检查调用此例程的每个子例程,以确保它不依赖于调用中的寄存器。并且每个例程都会调用这些例程等。更改常用的实用程序功能,最后必须验证整个程序;这种方式就是疯狂。

有两种可维护的解决方案:调用者保存它正在使用的所有寄存器,或者被调用者保存它更改的所有寄存器。通常,您希望代码在您获得的调用链中越来越复杂(并使用更少的寄存器),因此被调用者可能有一个较小的存储集。此外,函数调用的数量通常超过函数的数量,因此被调用者保存也会产生更少的代码。看起来MIPS遵循这个逻辑,并要求被调用者保存寄存器。有时,在具有大量寄存器的架构(例如,PowerPC)上,有一些被认为是“临时的”,因此被调用者不必保存它们;这是两种方法的结合。

答案 2 :(得分:0)

最常见的MIPS调用约定

  • 当您调用函数时,临时寄存器$t0,$t1,...,$t9可以被销毁(即更改但不会恢复)。
  • 调用函数后,$s0,$s1,...,$s7(被调用者已保存)寄存器必须保持不变。如果需要使用这些寄存器,您调用的函数通常会将被调用者保存的寄存器的值存储在堆栈中。被调用者保存的寄存器将在函数返回之前从堆栈中恢复。