gpt4 book ai didi

assembly - 低级语言的堆栈和堆栈框架

转载 作者:行者123 更新时间:2023-12-01 01:18:08 25 4
gpt4 key购买 nike

我试图围绕函数调用的概念进行探讨,因为它们与堆栈有关。这个问题是在低级语言而不是高级语言的背景下提出的。

到目前为止,据我了解,调用函数时,局部变量和参数存储在堆栈中的堆栈框架中。每个堆栈帧都与单个函数调用关联。我不清楚的部分是谁负责创建框架?我的程序是否应该查看程序中的函数声明,然后将局部变量手动复制到堆栈上的新框架中?

最佳答案

是...

假设您有允许递归的C语言。为此,功能的每个实例都必须独立于该功能的其他实例。堆栈是理想的地方,因为代码可以在不知道物理地址的情况下“分配”和引用分配中的项目,所有这些都可以通过引用进行访问。您所关心的就是在函数的上下文中跟踪该引用,并将堆栈指针还原到进入函数时的位置。

现在您必须有一个调用约定,一个适合递归的约定,等等。两个流行的选择(使用简化模型)是寄存器传递和堆栈传递。实际上,您可以拥有并且实际上将具有混合功能(基于寄存器的您将用完寄存器,并且必须针对其余参数恢复到堆栈)。

假设我正在谈论的虚构硬件神奇地处理了返回地址,而不会弄乱寄存器或堆栈。

注册通过。定义一组包含参数的硬件/处理器寄存器,假设r0始终是第一个参数,r1是第二个参数,r2是第三个参数。并假设返回值为r0(这是简化的)。

堆栈传递。让我们定义要压入堆栈的第一件事是最后一个参数,然后是紧随其后的直到第一个参数。当您返回时,假设返回值是堆栈中的第一件事。

为什么要声明调用约定?这样,调用方和被调用方都可以准确知道规则是什么,以及在哪里可以找到参数。从表面上看,寄存器传递看起来很棒,但是当寄存器用完时,必须将内容保存在堆栈中。当您想从被调用者转到另一个函数的调用者时,可能必须将项目保留在调用寄存器中,以免丢失这些值。您就在栈上。

int myfun ( int a, int b, int c)
{
a = a + b;
b+=more_fun(a,c)
return(a+b+c);
}


在调用more_fun之后使用a,b和c,more_fun至少需要r0和r1来传递参数a和c,因此您需要将r0和r1保存在某个位置,以便可以1)使用它们来调用more_fun ()和2),这样您就不会丢失从more_fun()返回后所需的a和b值。您可以将它们保存在其他寄存器中,但是如何保护这些寄存器不被调用函数修改。最终,东西被保存在堆栈中,该堆栈是动态的,可以通过引用而不是物理地址来访问。所以

有人想给myfun打电话,我们正在使用注册程序。

r0 = a
r1 = b
r2 = c
call myfun
;return value in r0

myfun:
r0 = r0 + r1 (a = a + b)
;save a and b so we dont lose them
push r0 (a)
push r1 (b)
r0 = r0 (a) (dead code, can be optimized out)
r1 = r2 (c)
call more_fun
;morefun returns something in r0
pop r1 (recover b)
r1 = r1 + r0 (b = b+return value)
pop r0 (recover a)
;r0 is used for returning a value from a function
r0 = r0 + r1 (= a+b)
r0 = r0 + r2 (=(a+b)+c)
return


调用函数(调用者)知道准备r0,r1,r2中的三个参数并取一个
返回值r0。被调用者知道接受r0,r1,r2作为传入参数并返回r0,并且知道它成为其他函数的调用者时必须保留一些内容。

如果我们使用堆栈通过调用约定传递参数

int myfun ( int a, int b, int c)
{
a = a + b;
b+=more_fun(a,c)
return(a+b+c);
}


现在我们必须制定一些寄存器规则,我们是否定义调用规则说:1)您可以销毁必须保留每个寄存器的任何寄存器(但是sp和pc和psr),2)当您返回调用函数永远不会看到其寄存器发生更改,或者您是否定义3)有些寄存器是临时寄存器,可以随意修改,而某些寄存器必须保留(如果使用)。我要说的是,为简单起见,您可以销毁除sp,pc和spr之外的寄存器。

我们还有一个要解决的问题。谁清理堆栈?当我调用morefun时,堆栈中有两个项目要进入,只有出口的返回值才清理堆栈。两种选择,呼叫者清理,被呼叫者清理,我和呼叫者清理一起去。这意味着被调用方必须按照发现的方式从带有堆栈的函数返回,将任何内容留在堆栈上,并且不会从堆栈中删除太多内容。

呼叫者:

push c
push b
push a
call myfun
pop result
pop and discard
pop and discard


假设使用此硬件,堆栈指针sp指向堆栈上的当前项目

myfun:
;sp points at a
load r0,[sp+0] (get a)
load r1,[sp+1] (get b)
add r0,r1 (a = a+b)
store [sp+0],r0 (the new a is saved)
;prepare call to more_fun
load r0,[sp+2] (get c)
load r1,[sp+0] (get a)
push r0 (c)
push r1 (a)
call more_fun
;two items on stack have to be cleaned, top is return value
pop r0 (return value)
pop r1 (discarded)
;we have cleaned the stack after calling more_fun, our offsets are as
;they were when we were called
load r1,[sp+1] (get b)
add r1,r0 (b = b + return value)
store [sp+1],r1
load r0,[sp+0] (get a)
load r1,[sp+1] (get b)
load r2,[sp+2] (get c)
add r0,r1 (=a+b)
add r0,r2 (=(a+b)+c)
store [sp+0],r0 (return value)
return


因此,我即时编写了所有这些内容,可能存在错误。所有这些的关键是您必须定义一个调用约定,并且如果每个人(调用方和被调用方)都遵循该调用约定,则使编译变得容易。诀窍在于制定一个有效的调用约定,如您在上面看到的,我们不得不修改约定并添加规则以使其即使对于这样一个简单的程序也能起作用。

堆栈框架呢?

int myfun ( int a, int b)
{
int c;
c = a + b;
c+=more_fun(a,b)
return(c);
}


使用基于堆栈

呼叫者

push b
push a
call myfun
pop result
pop and discard


被叫者

;at this point sp+0 = a, sp+1 = b, but we need room for c, so
sp=sp-1 (provide space on stack for local variable c)
;sp+0 = c
;sp+1 = a
;sp+2 = b
load r0,[sp+1] (get a)
load r1,[sp+2] (get b)
add r0,r1
store [sp+0],r0 (store c)
load r0,[sp+1] (get a)
;r1 already has b in it
push r1 (b)
push r0 (a)
call more_fun
pop r0 (return value)
pop r1 (discarded to clean up stack)
;stack pointer has been cleaned, as was before the call
load r1,[sp+0] (get c)
add r1,r0 (c = c+return value)
store [sp+0],r1 (store c)(dead code)
sp = sp + 1 (we have to put the stack pointer back to where
;it was when we were called
;r1 still holds c, the return value
store [sp+0],r1 (place the return value in proper place
;relative to callers stack)
return


被调用方如果使用堆栈并移动堆栈指针,则必须将其放回原处
是当它被称为。通过在堆栈上添加适当数量的东西以进行本地存储,可以创建堆栈框架。您可能具有局部变量,通过编译过程,您可能提前知道必须保留一定数量的寄存器。最简单的方法是将所有这些累加起来,然后将整个函数的堆栈指针移动一次,然后在返回之前将其放回一次。您可以变得更聪明,并且可以在调整偏移量的同时不断移动堆栈指针,这使代码编写起来更加困难,并且更容易出错。像gcc这样的编译器倾向于将堆栈指针移到函数中,并在离开之前将其返回。

一些指令集会在调用时将内容添加到堆栈中,并在返回时将其删除,您必须相应地调整偏移量。同样,围绕另一个函数的调用进行的创建和清理可能需要与堆栈的硬件使用相关的处理(如果有)。

可以说,当您进行呼叫时,硬件会将返回值压入堆栈的顶部。

int onefun ( int a, int b )
{
return(a+b)
}

onefun:
;because of the hardware
;sp+0 return address
;sp+1 a
;sp+2 b
load r0,[sp+1] (get a)
load r1,[sp+2] (get b)
add r1,r2
;skipping over the hardware use of the stack we return on what will be the
;top of stack after the hardware pops the return address
store [sp+1],r1 (store a+b as return value)
return (pops return address off of stack, calling function pops the other two
;to clean up)


某些处理器在调用函数时使用寄存器来保存返回值,有时
硬件决定哪个寄存器,有时编译器会选择一个并将其用作寄存器。
惯例。如果您的函数未调用任何其他函数,则可以不使用返回地址寄存器并将其用于返回,也可以在某个时候将其压入堆栈,然后在返回之前将其弹出并使用它返回。如果您的函数确实调用了另一个函数,则必须保留该返回地址,以便对下一个函数的调用不会破坏它,并且您将找不到回家的路。因此,您可以将其保存在另一个寄存器中,也可以放入堆栈中

使用我们定义的上述寄存器调用约定,还有一个名为rx的寄存器,在进行调用时,硬件会为您将返回地址放置在rx中。

int myfun ( int a, int b)
{
return(some_fun(a+b));
}

myfun:
;rx = return address
;r0 = a, first parameter
;r1 = b, second parameter
push rx ; we are going to make another call we have to save the return
; from myfun
;since we dont need a or b after the call to some_fun we can destroy them.
add r0,r1 (r0 = a+b)
;we are all ready to call some_fun first parameter is set, rx is saved
;so the call can destroy it
call some_fun
;r0 is the return from some_fun and is going to be the return from myfun,
;so we dont have to do anything it is ready
pop rx ; get our return address back, stack is now where we found it
; one push, one pop
mov pc,rx ; return

关于assembly - 低级语言的堆栈和堆栈框架,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10824364/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com