无法弄清楚程序中堆栈的使用?
好的,所以我的任务是修改此代码以计算大写和小写元音。该程序的重点是演示如何使用堆栈来保存跨函数调用的数据:
##
## 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 中,那么有什么意义呢?看起来好像只是最后恢复了原始值。哎呀,那只是为了证明它是可能的吗?
Ok, so my task was to modify this code to count both upper and lower case vowels. The point of the program is to demonstrate the use of stack to preserve data across function calls:
##
## 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
my modified code that works:
##
## 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"
I don't understand what the lw block at the end is for. The program stores the count in s0 and t0 respectively, so whats the point? It looks as though its just restoring the original values at the end. Whoop de do was that there just to demonstrate that its possible?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我对 MIPS 不太了解,但我假设这个想法与 x86 类似。
根据你的说法,最后的LW正在恢复原始值。发生这种情况的原因是,您可以在另一个函数内部调用一个函数(初始风格),而不必担心丢失放置在堆栈上但未分配给内存的变量和值(例如迭代器等)。
例如,假设您正在迭代一整页文本,一次一行。您将把外循环(页行)的迭代器存储在寄存器中。现在,当您输入函数来计算元音数量时,您不想担心丢失该值,因此它会被推入堆栈。您的元音计数器将运行它应该做的事情,使用它想要的任何寄存器,然后当它完成时,它将(取决于您的方法)将堆栈中的值恢复到原始位置。这样,内部函数不会与外部函数混淆,并且您的寄存器不会被被调用方法的函数破坏。
I don't know much about MIPS, but I'm assuming that the idea is similar to x86.
Just from what you said, the last LW is restoring the original values. The reason this occurs is so you can call a function inside of another function (inception style) and not worry about losing variables and values placed on the stack that weren't allocated to memory (things like iterators etc).
For example, lets say you're iterating through a whole page of text taking it one line at a time. You'll store the iterator of the outer loop (the page line) in a register. Now when you enter the function to count the number of vowels, you don't want to worry about losing that value so it gets pushed onto the stack. Your vowel counter will run do what it's supposed to do, use whatever registers it wants, and then when it completes it will (depending on your methodology) restore the values from the stack to their original places. That way the inner function doesn't mess with the outer function and you don't have your registers getting smashed by the called method's functions.
我不熟悉 MIPS 汇编,但一般来说,每个平台都有关于子例程如何行为的约定。这些约定之一通常是子例程必须保留哪些 CPU 寄存器。这些约定共同构成了 ABI。
可以这样想:当您的程序只有几个子例程时,每次调用它时很容易跟踪“是的,这个例程会破坏寄存器 X”。但随着程序的增长,这变得非常困难。想象一下更改函数以使用新寄存器的难度 - 然后您必须检查调用该例程的每个子例程,以确保它在调用过程中不依赖于寄存器。以及调用这些例程的每个例程等等。更改常用的实用程序函数,您最终必须验证整个程序;这就是疯狂。
对此有两种可维护的解决方案:调用者保存其正在使用的所有寄存器,或者被调用者保存其更改的所有寄存器。通常,您期望代码在调用链中越深入,复杂性越低(并且使用的寄存器越少),因此被调用者可能需要保存的集合较小。此外,函数调用的数量通常超过函数的数量,因此被调用者保存也会产生更少的代码。看起来MIPS遵循这个逻辑,并要求被调用者保存寄存器。有时,在具有大量寄存器的体系结构(例如,PowerPC)上,有一些寄存器被认为是“临时的”,因此被调用者不必保存它们;这是两种方法的结合。
I'm not familiar with MIPS assembly, but in general each platform has conventions as to how subroutines are supposed to behave. One of those conventions is usually around which CPU registers a subroutine must preserve. The conventions, taken together, form the ABI.
Think of it this way: When you have a program with only a few subroutines, its easy enough to keep track of "yeah, this routine destroys register X" each time you call it. But as your program grows, that becomes very difficult. Imagine the difficulty of changing a function to use a new register—you'd have to then check each subroutine that calls this routine, to make sure it doesn't rely on the register across the call. And every routine that calls those routines, etc. Change a commonly-used utility function, and you wind up having to verify the entire program; in this way lies madness.
There are two maintainable solutions to this: either the caller saves all registers it is using, or the callee saves all registers it changes. Normally, you'd expect code to get less complex (and use less registers) the further in the call chain you get, so the callee probably has a smaller set to save. Further, the number of function calls normally exceeds the number of functions, so callee saving also produces less code. It looks like MIPS follows this logic, and requires the callee to save the registers. Sometimes, on architectures with a lot of registers (e.g., PowerPC) there are some which are considered "temporary" and thus the callee doesn't have to save them; this is a combination of the two approaches.
在最常见的 MIPS 调用约定中,
$t0,$t1,...,$t9
可能会被销毁(即更改且不恢复)。$s0,$s1,...,$s7
(被调用者已保存)寄存器必须保持不变。如果您调用的函数需要使用这些寄存器,则通常会将被调用者保存的寄存器的值存储在堆栈上。被调用者保存的寄存器将在函数返回之前从堆栈中恢复。In the most common MIPS calling convention
$t0,$t1,...,$t9
can be destroyed (i.e. changed and not restored) when you call a function.$s0,$s1,...,$s7
(callee saved) registers must remain unchanged after you call a function. The function you call will typically store the value of the callee saved registers on the stack if it needs to use these registers. The callee saved registers will be restored from the stack right before the function returns.