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