JUMP 和 CALL 指令有何不同?它与 GOTO 或过程调用等更高级别的概念有何关系?(我在比较中正确吗?)
这就是我的想法:
JUMP 或 GOTO 是将控件转移到另一个位置,并且控件不会自动返回到调用它的位置。
另一方面,CALL 或过程/函数调用返回到调用它的位置。由于它们本质上的这种差异,语言通常使用堆栈,并且堆栈帧被推送以“记住”每个调用过程返回的位置。这种行为也适用于递归过程。但是,在尾递归的情况下,无需为每次调用“推送”堆栈帧。
您的回答和评论将不胜感激。
JUMP 和 CALL 指令有何不同?它与 GOTO 或过程调用等更高级别的概念有何关系?(我在比较中正确吗?)
这就是我的想法:
JUMP 或 GOTO 是将控件转移到另一个位置,并且控件不会自动返回到调用它的位置。
另一方面,CALL 或过程/函数调用返回到调用它的位置。由于它们本质上的这种差异,语言通常使用堆栈,并且堆栈帧被推送以“记住”每个调用过程返回的位置。这种行为也适用于递归过程。但是,在尾递归的情况下,无需为每次调用“推送”堆栈帧。
您的回答和评论将不胜感激。
如果您在谈论 x86 程序集中的 CALL/JMP 或类似的东西,那么您基本上是对的。主要区别在于:
通常,CALL 只是一个使用 JMP 实现的便利函数。你可以做类似的事情
movl $afterJmp, -(%esp)
jmp location
afterJmp:
而不是呼叫。
我想你已经有了大致的想法。
这取决于架构,但通常在硬件级别:
跳转指令将更改程序计数器以在程序的不同部分继续执行。
一条调用指令会将当前程序位置(或当前位置+1)压入调用堆栈并跳转到程序的另一部分。然后返回指令将从调用堆栈中弹出该位置并跳回原始位置(或原始位置 + 1)。
因此,跳转指令接近于GOTO
,而调用指令接近于过程/函数调用。
另外,由于在进行函数调用时使用了调用堆栈,因此通过递归将过多的返回地址压入调用堆栈会导致堆栈溢出。
在学习汇编时,我发现处理RISC处理器比处理 x86 处理器更容易,因为它往往具有更少的指令和更简单的操作。
您对跳转和呼叫之间的区别完全正确。
在具有尾递归的单个函数的示例情况下,编译器可能能够重用现有的堆栈帧。但是,使用相互递归函数可能会变得更加复杂:
void ping() { printf("ping\n"); pong(); }
void pong() { printf("pong\n"); ping(); }
考虑 ping() 和 pong() 是采用不同数量参数的更复杂函数的情况。Mark Probst 的论文非常详细地讨论了 GCC 的尾递归实现。
对您的想法的一个更正:不仅使用尾递归,而且通常使用尾调用,我们不需要堆栈帧,因此可以在那里简单地 JMP(前提是参数已正确设置)。
根据微处理器,首先检查条件,然后执行跳转操作(转到其他代码)并且不返回。调用操作就像c
语言中的函数 callig,当函数执行时,它会返回以完成其执行。