我读到,当程序调用函数时,被调用函数必须知道如何返回给调用者。
我的问题是:被调用函数如何知道如何返回给它的调用者?是否有通过编译器在幕后工作的机制?
我读到,当程序调用函数时,被调用函数必须知道如何返回给调用者。
我的问题是:被调用函数如何知道如何返回给它的调用者?是否有通过编译器在幕后工作的机制?
编译器遵循特定的“调用约定”,定义为您所针对的 ABI 的一部分。该调用约定将包括一种让系统知道要返回哪个地址的方法。调用约定通常利用硬件对过程调用的支持。例如,在 Intel 上,返回地址被压入堆栈:
...处理器将
EIP
寄存器的值(包含指令后CALL
指令的偏移量)压入堆栈(稍后用作返回指令指针)。
从函数返回是通过以下ret
指令完成的:
...处理器将返回指令指针(偏移量)从堆栈顶部弹出到
EIP
寄存器中,并在新指令指针处开始程序执行。
相比之下,在 ARM 上,返回地址放在链接寄存器中:
BL
and指令将BLX
下一条指令的地址复制到lr
(r14
,链接寄存器)。
返回通常通过执行movs pc, lr
将地址从链接寄存器复制回程序计数器寄存器来完成。
参考:
编译器知道如何调用函数以及使用哪种调用约定。例如,在 C 中,函数的参数被压入堆栈。调用者负责清除堆栈,因此被调用函数不必删除参数。其他调用约定可以包括将参数推送到堆栈上,并且被调用的函数必须清理它。在这种情况下,生成的代码是这样的,该函数在返回之前纠正了堆栈。其他调用约定可能会在寄存器中传递参数,因此在这种情况下,被调用函数也不必小心。
CPU 具有调用子程序的机制。这会将当前执行地址存储在堆栈上,然后将处理转移到新地址。当函数完成时,它会执行一个 return 语句,该语句将获取调用者地址并在那里继续执行。
如果返回地址被破坏,因为堆栈没有正确清理uo,或者内存被覆盖,那么你会得到未定义的行为。当然,具体的实现细节取决于所使用的平台。
堆栈使这成为可能(尤其是在类似 Intel 的系统上)。假设我们有一个方法caller
,其中包括一个int
它保存在本地的方法。
当caller(
调用target(
该 int 时,必须保存。它与发出调用的地址一起放在堆栈上。target(
可以执行其逻辑,创建自己的局部变量,并调用其他方法。它的局部变量将与调用的地址一起放入堆栈。
结束时target(
,堆栈“展开”。target(
包含的局部变量的堆栈顶部被删除。
当方法递归太远时,堆栈可能会变得太大,并且可能会发生“堆栈溢出”。
它需要被调用者和调用者之间的合作。
调用者同意将被调用者应该返回的地址提供给被调用者(通常通过将其压入堆栈或将其传递到寄存器中),并且被调用者同意在执行完成后返回该地址。