调用后被调用函数如何返回其调用者?

时间:2013-07-07 18:03:59

标签: c compiler-construction abi function-call stackframe

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

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

4 个答案:

答案 0 :(得分:12)

编译器遵循特定的“调用约定”,定义为您要定位的ABI的一部分。该调用约定将包括一种系统知道返回什么地址的方法。调用约定通常利用硬件对过程调用的支持。例如,在Intel上,返回地址被推送到堆栈:

  

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

从函数返回是通过ret指令完成的:

  

...处理器将返回指令指针(偏移量)从堆栈顶部弹出到 EIP 寄存器中,并在新指令指针处开始执行程序。

相比之下,在ARM上,返回地址放在链接寄存器中:

  

BL BLX 说明会将下一条指令的地址复制到 lr r14 ,链接注册)。

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

参考文献:

  1. Intel Software Developers Manual
  2. ARM Information Center

答案 1 :(得分:8)

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

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

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

答案 2 :(得分:4)

这可以通过堆栈实现(特别是在类似Intel的系统上)。假设我们有一个方法caller,其中包含一个int,它保留在本地。

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

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

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

答案 3 :(得分:4)

它需要被叫方和来电者之间的合作。

调用者同意给出被调用者应该返回给被调用者的地址(通常是通过在栈上推送它,或者将其传递给寄存器),并且被调用者同意在完成执行时返回该地址。