在流行的 x86 架构上,可以使用 jmp
类型指令之一以及 call
指令来控制执行流程。但两者都是基本的,还是一个是另一个的“语法糖”?
例如,push
指令是语法糖:push eax
相当于mov [esp], eax
后跟子特别是,4
。因此,mov
是唯一的基本数据操作。
但是call
也是如此吗?是否有其他一些指令可以达到相同的效果?
在伪代码中,如果我有权访问,我可以使用jmp
编写函数调用
指令指针:
bar:
push eip ;; pseudo-code! ;; call foo
jmp foo
foo:
pop eax ;; ret
jmp [eax]
这不是有效的 x86,因为无法直接读取指令指针寄存器 eip
。到目前为止我见过的所有获取eip
值的“技巧”都是使用call
。或者 call
实际上是在 x86 上读取指令指针的规范方式?作为一个额外的问题,是否每个可以实现 C 的体系结构都需要一种读取指令指针的方法,以便可以实现函数指针?
int main(int argc, char * argv[])
{
int (*p)() = strtoul(argv[1]);
return p(); // how to implement without
// accessing the instruction pointer?
}
[这不是一个深奥的问题;知道函数不仅仅是高级编程语言引入的抽象概念,而且实际上是硬件固有的,我会感到非常高兴。]
最佳答案
机器指令集可能非常简单;请参阅Wolfram's 2-state, 3 symbol Turing machine.问题是这样的机器很难编程,也很难快速运行。
您可以使用其他指令完全模拟调用返回:
; Simulate "call abc":
push offset next_location
jmp abc
next_location:
; Simulate "ret"
pop eax
jmp eax
您显然可以使用 mov 指令模拟入栈/出栈,并通过添加问题中提到的常量来调整寄存器。
人们将指令添加到指令集中,以使通用序列变得高效。因此,“call”之所以存在,是因为它执行了一项常见的有用任务(并且“call/ret”对可以通过硬件使用内部返回地址堆栈进行优化,以避免指令管道中断;这是真正的性能优势)。
所以,是的,您可以使用计算机提供的最小指令集来实现汇编语言的目的。但正确的附加说明在实践中确实有帮助。 (出于善意,您可以添加您认为可能有用的说明。通常它们没有帮助,这就是 source of the original RISC/CISC debate )。
Goofball 最小机器:
20世纪70年代生产的真正的小型机(“GRI”)只有一条指令:MOV LOC1到LOC2;目标地点是神奇的,这就是真正的工作是如何完成的。通过将某些内容移动到位置 33(编造),副作用是该值被添加到可以从位置 32 读取的累加器中。它有很多位置可以做有趣、方便的事情,所以本质上添加了一个新的有趣寄存器只是添加有用指令的一个奇怪变体。我怀疑 JMP 指令包括将一个常量移动到充当程序计数器的位置。 (如果您觉得这很奇怪,那么您还没有见过 PDP-11)。
理论机器是具有单个指令“SUB LOC1,如果为负,则从 LOC2 跳转到 LOC3”的机器。这台机器是通用的,但在你遇到第一个玩具问题后,编程就不那么有趣了。
值得注意的是,这两个指令集都没有操作码位或寻址模式位。指令解码器确实很简单。
关于c - "functions"是 x86 硬件的基本部分吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17394085/