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