概述
我知道嵌套函数调用中的数据会进入堆栈。堆栈本身实现了一种逐步方法,用于在函数被调用或返回时从堆栈中存储和检索数据。这些方法的名称最著名的是Prologue和 结语。
我尝试搜索有关此主题的材料没有成功。 你们是否了解有关函数序言和尾声在C语言中通常如何工作的任何资源(网站,视频,文章)? 或者,如果您能解释会更好。
附注:我只想提供一些一般性的看法,而不是太详细。
您可以搜索有关编译器和代码生成的材料。
有很多资源可以解释这一点:
功能序言(Wikipedia)
x86拆卸/调用约定(WikiBooks)
编写Prolog / Epilog代码(MSDN)的注意事项
仅举几例。
基本上,正如您所描述的那样,"堆栈"在程序执行中有几个用途:
调用函数时跟踪返回的位置
在函数调用的上下文中存储局部变量
将参数从调用函数传递给被调用者。
问题是在函数开始时发生的事情。它的责任是建立被调用函数的堆栈框架。结语正好相反:它是函数中最后发生的事情,其目的是恢复调用(父)函数的堆栈框架。
在IA-32(x86)cdecl中,该语言使用ebp寄存器来跟踪函数的堆栈帧。处理器使用esp寄存器指向堆栈上的最新加法(最高值)。
call指令有两件事:首先,它将返回地址压入堆栈,然后跳转到被调用的函数。 call之后,esp指向堆栈上的返回地址。
然后执行序言:
push ebp ; Save the stack-frame base pointer (of the calling function).
mov ebp, esp ; Set the stack-frame base pointer to be the current
; location on the stack.
sub esp, N ; Grow the stack by N bytes to reserve space for local variables
在这一点上,我们有:
...
ebp + 4: Return address
ebp + 0: Calling function's old ebp value
ebp - 4: (local variables)
...
结语:
mov esp, ebp ; Put the stack pointer back where it was when this function
; was called.
pop ebp ; Restore the calling function's stack frame.
ret ; Return to the calling function.
只是为了完整起见,值得一提的是ret指令执行与call指令相反的操作,即ret指令还必须做两件事-使用esp将返回地址弹出堆栈,然后跳转到该地址 从那里恢复执行。
这就引出了一个问题,即谁清理何时传递的参数? 我的猜测是,根据x86 cdecl调用约定,调用者必须是在调用调用指令之前推送args的那个,因此,必须是同一调用者,在调用之后需要从堆栈中清除args。 ret指令。
C函数调用约定和堆栈很好地解释了调用堆栈的概念
功能序言简要解释了汇编代码以及操作方式和原因。
功能外生基因
我参加聚会已经很晚了,我敢肯定,自从提出问题以来的最近7年中,您对事情有了更清晰的了解,当然,如果您选择进一步探讨这个问题。但是,我认为我仍然会特别关注序言和尾声部分的原因。
同样,被接受的答案优雅而简洁地解释了结尾词和序言的方式,并提供了很好的参考。我只打算用"为什么"(至少是逻辑上的"为什么")部分补充这个答案。
我将从接受的答案中引用以下内容,并尝试扩展其解释。
In IA-32 (x86) cdecl, the ebp register is used by the language to keep
track of the function's stack frame. The esp register is used by the
processor to point to the most recent addition (the top value) on the
stack.
Ok.
The call instruction does two things: First it pushes the return
address onto the stack, then it jumps to the function being called.
Immediately after the call, esp points to the return address on the
stack.
Ok.
上面引号的最后一行是immediately after the call, esp points to the return address on the stack.
为什么?
因此,假设我们当前正在执行的代码具有以下情况,如下图(非常糟糕地绘制)所示:
因此,我们要执行的下一条指令位于地址2。这就是EIP指向的位置。当前指令具有一个函数调用(它将在内部转换为汇编call指令)。
现在,理想情况下,由于EIP指向下一条指令,因此实际上就是要执行的下一条指令。但是,由于当前执行流路径有所偏离,(现在由于call而可以预料)EIP的值将发生变化。为什么?因为现在可能需要执行另一条指令,该指令可能位于其他地方,例如地址1234(或其他位置)。但是,为了按照程序员的意图完成程序的执行流程,在完成转移活动之后,控件必须返回到地址2,因为如果没有发生转移,则下一步应该执行该地址。让我们将此地址2称为正在生成的call的return address。
问题1
因此,在实际发生转移之前,必须将返回地址2临时存储在某个地方。
将其存储在任何可用寄存器或某个内存位置等中的选择可能很多,但是出于(我相信有充分的理由)决定将返回地址存储在堆栈中。
因此,现在需要做的是增加ESP(堆栈指针),以使堆栈顶部现在指向堆栈上的下一个地址。因此,指向地址(例如292)的TOS(递增之前的TOS)现在递增并开始指向地址293。这就是我们放置return address 2的位置。所以像这样:
这样看来,现在我们已经实现了将返回地址临时存储在某个地方的目标。现在我们应该使转移call。我们可以。但是有一个小问题。在执行被调用函数期间,可以多次操作堆栈指针以及其他寄存器值。
问题2
因此,尽管我们的返回地址仍然存储在堆栈中的位置293中,在被调用函数完成执行之后,执行流程如何知道它现在应该转到293并在其中找到返回地址?
因此(再次相信,有充分的理由)解决上述问题的一种方法是将堆栈地址293(返回地址在其中)存储在称为EBP的(指定的)寄存器中。但是,EBP的内容又如何呢?那不会被覆盖吗?当然,这是有道理的。因此,让我们将EBP的当前内容存储到堆栈上,然后将此堆栈地址存储到EBP中。像这样:
堆栈指针增加。 EBP的当前值(表示为EBP'),即xxx,存储在堆栈的顶部,即地址294。现在,我们已备份了EBP的当前内容,可以放心地放EBP上的任何其他值。因此,我们将当前堆栈顶部的地址(即地址294)放在EBP中。
有了上述策略,我们就可以解决上述问题2。怎么样?因此,现在当执行流想要知道应该从哪里获取返回地址时,它将:
首先从EBP中获取值,然后将ESP指向该值。在我们的情况下,这将使TOS(堆栈顶部)指向地址294(因为这是存储在EBP中的内容)。
然后它将恢复先前的EBP值。为此,只需将值294(TOS)取为xxx(实际上是EBP的旧值),然后将其放回EBP。
然后它将使堆栈指针递减,以转到堆栈中的下一个较低地址,在本例中为293。从而最终达到293(请参阅第2个问题)。在那里可以找到寄信人地址,即2。
最终它将把这2弹出到EIP中,记住,如果没有发生转移,则应该执行该指令。
我们刚才看到的所有杂物都将临时执行返回地址,然后取回地址,这正是通过prolog函数(在函数call之前)和Epilog(在函数< x1>)。 方法已经被回答,我们也只是回答了原因。
只是个结尾说明:为简洁起见,我没有考虑堆栈地址可能会反过来增长这一事实。
好。
每个函数都有相同的序言(函数代码的开头)和结尾(函数的结尾)。
序言:序言的结构如下:
推息
mov esp,ebp
结语:序言的结构如下:
离开
退回
更详细:什么是序言和结尾
取决于调用约定
最后
以上就是灵巧保温杯为你收集整理的c语言截尾的方式,关于c ++:C中的函数序言和结尾的全部内容,希望文章能够帮你解决c语言截尾的方式,关于c ++:C中的函数序言和结尾所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复