2013-07-07 77 views
12

我读过,当程序进行函数调用时,被调用函数必须知道如何返回给调用者。被调用后,被调用的函数如何返回给调用者?

我的问题是:被调用函数如何知道如何返回到调用者?是否有通过编译器在幕后工作的机制?

+1

您应该阅读[this](http://en.wikipedia.org/wiki/Call_stack#Structure)这是非常好的答案:[Chapter 11 - Procedures](http://pages.cs.wisc。 edu /〜smoler/x86text/lect.notes/procedures.html)如果你喜欢视频:[黑客组装入门(第11部分)函数栈](http://www.progamercity.net/code-tut/168- assembly-language-primer-hackers-video-series.html)你会喜欢这里的所有视频。 –

回答

12

编译器遵循特定的“调用约定”,该约定被定义为您定位的ABI的一部分。该调用约定将包括系统知道要返回的地址的方式。调用约定通常利用硬件对程序调用的支持。英特尔,例如,返回地址被压入堆栈:

...处理器推动EIP寄存器的值(其中包含CALL指令之后的指令的偏移量)在栈上(稍后用作返回指令指针)。

...处理器从堆栈中弹出的顶部返回指令指针(偏移)到EIP寄存器并且开始:

从函数返回经由ret指令完成程序在新的指令指针处执行。

对比,对ARM,返回地址被放入链接寄存器:

BLBLX指令复制下一条指令的地址为lrr14,链接寄存器)。

返回通常通过执行movs pc, lr将地址从链接寄存器复制回程序计数器寄存器来完成。

参考文献:

  1. Intel Software Developers Manual
  2. ARM Information Center
4

这是由叠层(尤其是在Intel样系统)成为可能。假设我们有一个方法caller,它包含了一个本地保存的int

caller(调用target( int必须保存。它被放置在堆栈上,以及呼叫的地址。 target(可以执行其逻辑,创建自己的局部变量,并调用其他方法。它的局部变量将与电话的地址一起放入堆栈。

target(结束时,堆栈被“展开”。包含target(的局部变量的堆栈顶部被删除。

当方法递归太多时,堆栈可能会变得太大,并且可能发生“堆栈溢出”。

8
  1. 编译器知道如何调用函数以及使用哪种调用约定。例如在C中,函数的参数被压入堆栈。调用者负责清除堆栈,所以被调用的函数不必删除参数。其他调用约定可以包括将参数推入堆栈,被调用函数必须清除它。在这种情况下,生成的代码是这样的,函数可以在返回之前纠正堆栈。 Ohter调用约定可能将参数传递到寄存器中,因此在这种情况下,被调用函数也不必小心。

  2. CPU有调用子程序的机制。这会将当前执行地址存储在堆栈上,然后将处理转移到新地址。当函数完成时,它执行一个return语句,它将获取调用者地址并在那里恢复执行。

如果返回地址被破坏,因为堆栈没有正确清理uo,或者内存被覆盖,那么你会得到未定义的行为。当然,具体的实施细节取决于所使用的平台。

4

它需要被叫方和主叫方之间的合作。

主叫方同意给被叫方返回给被叫方的地址(通常通过将其推入堆栈或通过将其传送到注册表中),并且被叫方同意在完成时返回到该地址执行。

相关问题