12

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

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

4

4 回答 4

12

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

...处理器将EIP寄存器的值(包含指令后CALL指令的偏移量)压入堆栈(稍后用作返回指令指针)。

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

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

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

BLand指令将BLX下一条指令的地址复制到lr( r14,链接寄存器)。

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

参考:

  1. 英特尔软件开发人员手册
  2. ARM 信息中心
于 2013-07-07T18:06:55.923 回答
8
  1. 编译器知道如何调用函数以及使用哪种调用约定。例如,在 C 中,函数的参数被压入堆栈。调用者负责清除堆栈,因此被调用函数不必删除参数。其他调用约定可以包括将参数推送到堆栈上,并且被调用的函数必须清理它。在这种情况下,生成的代码是这样的,该函数在返回之前纠正了堆栈。其他调用约定可能会在寄存器中传递参数,因此在这种情况下,被调用函数也不必小心。

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

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

于 2013-07-07T18:57:08.687 回答
4

堆栈使这成为可能(尤其是在类似 Intel 的系统上)。假设我们有一个方法caller,其中包括一个int它保存在本地的方法。

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

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

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

于 2013-07-07T18:06:56.180 回答
4

它需要被调用者和调用者之间的合作。

调用者同意将被调用者应该返回的地址提供给被调用者(通常通过将其压入堆栈或将其传递到寄存器中),并且被调用者同意在执行完成后返回该地址。

于 2013-07-07T19:08:57.607 回答