所有程序最终都会转换为汇编指令吗?

24

据我了解,处理器电路从芯片到芯片会有很大差异,因此执行相同高级代码可能需要不同的低级指令。所有程序最终是否都转换为汇编语言后成为原始机器码,或者这一步骤已不再必要?

如果是这样,那么处理器何时开始执行自己独特的指令集?这是代码的最低层次,所以程序指令是否在这一点上按位由处理器执行?

最后,所有架构都需要汇编语言吗?


1
请参见https://dev59.com/2Gct5IYBdhLWcg3wjuAj?rq=1,了解有关硬件如何运行汇编语言的信息。 - bengoesboom
8个回答

27

汇编语言是一种人类可读的表达器处理器执行指令的方式(这些指令是二进制数据,非常难以被人类管理)。因此,如果机器指令不是由人类生成的,则不需要使用汇编步骤,尽管有时出于方便而使用。如果从诸如C ++之类的语言编译程序,则编译器可以直接生成机器代码,而无需经过汇编代码的中间阶段。尽管如此,许多编译器都提供了生成汇编代码的选项,以使人类更容易检查所生成的内容。

许多现代语言(例如Java和C#)被编译成所谓的字节码。这是CPU不直接执行的代码,而是一种中间形式,在程序执行时可能会即时(JIT-ted)编译为机器代码。在这种情况下,会生成与CPU相关的机器代码,但通常不通过人类可读的汇编代码进行。


10
汇编语言只是机器代码的可读的文本表示形式。它存在是为了方便(人类)程序员。它并不必须作为生成机器代码的中间步骤。有些编译器确实会生成汇编语言,然后调用汇编器将其转换为机器代码。但是由于省略该步骤会导致更快的编译速度(而且并不难做到),编译器将(广义上)趋向于直接生成机器代码。不过,编译成汇编语言的选项很有用,可以用来检查结果。
对于您最后一个问题,汇编语言是为人类方便而存在的,因此没有架构真正“需要”它。如果真的想这么做,就可以创建一个没有汇编语言的架构。但在实践中,所有架构都有汇编语言。首先,创建新的汇编语言非常容易:为所有机器操作码和寄存器提供文本名称,添加一些语法以表示不同的寻址模式,你几乎就完成了。即使所有代码都直接从高级语言转换为机器语言,你仍然需要一种汇编语言,作为查找编译器错误时反汇编和可视化机器代码的一种方式。

1
第二段相关的问答:既然有编译器,为什么我们还需要汇编语言? - 你的回答基本上就是我在那里写的:我们有汇编语言是为了让人类编译器开发人员和其他人查看、思考和在与其他人讨论时使用。(以及用于性能实验/微基准测试的工具。) - Peter Cordes

5
每个通用CPU都有自己的指令集。也就是说,当执行特定的字节序列时,会对寄存器和内存产生已知且有文档记录的影响。汇编语言是一种方便的方式,可以将这些指令写下来,以便人们可以阅读、书写并理解它们的作用,而不必一直查找命令。可以相当肯定地说,对于每个现代CPU,都存在一种汇编语言。
现在,关于程序是否转换为汇编。让我们从CPU不执行汇编代码开始。它执行机器码,但机器码命令与汇编行之间存在一对一的对应关系。只要记住这种区别,就可以说“现在CPU执行MOV,然后是ADD”等等。当然,CPU执行与MOV命令相对应的机器码。
话虽如此,如果您的语言编译成本机代码,则在执行之前确实会将程序转换为机器码。一些编译器(并非所有编译器)通过发出汇编源代码并让汇编程序执行最终步骤来完成此操作。除非您告诉编译器保持其完整性,否则该步骤通常被很好地隐藏。汇编表示仅在编译过程中短暂存在,除非您告诉编译器保持其完整性。
其他编译器不使用汇编步骤,但如果被要求,则会发出汇编。例如,Microsoft C++采用选项/FA - 发出带有对象文件的汇编清单。
如果它是解释语言,则不会显式转换为机器码。源代码行由语言解释器执行。面向字节码的语言(Java、Visual Basic)介于两者之间;它们编译成的代码与机器码不同,但比高级源代码更容易解释。对于这些语言,也可以说它们没有被转换为机器码。

1
这是一个相当复杂的问题。并不是所有的程序都会被转换成汇编语言。如果我们排除即时编译,像 Ruby、Lisp 和 Python 这样的解释性语言以及在虚拟机上运行的程序(例如 Java 和 C#),它们并不会被转换成汇编语言。相反,存在一个现有的程序,一个解释器或虚拟机,接收源代码(解释)或字节码(VM)(这不是计算机的汇编语言)并运行它们。解释器知道当它看到特定的输入序列时该做什么,并采取正确的操作,即使它之前没有看到过这个特定的输入。
编译程序,像你用 C 或 C++ 编写的程序,可以在编译过程中被转换成汇编语言,然后再转换成机器语言。通常跳过这一步以加快速度。一些编译器,如 LLVM,输出通用位码,因此它们可以将生成位码的编译器部分与将位码转换为机器码的部分分开,从而实现跨架构重用。
然而,即使操作系统将CPU视为消耗机器码的东西,许多CPU都具有较低级别的微代码。指令集中的每个指令(汇编级别)都被CPU实现为一系列更简单的微代码操作。在不同的CPU上,指令集可以保持不变,而实现指令的微代码则发生变化。将指令集视为CPU的API。

1
当然,解释器本身由机器码指令组成(或者本身是JIT编译或解释的,直到某个较低层次包含预先编译或手写的机器码)。但是,是的,一个解释程序只是解释器程序的“数据”。 - Peter Cordes

1
所有处理器都使用位,我们称之为机器代码,出于不同的原因,它可以呈现出非常不同的风格,从改进老鼠夹到保护创意专利。从用户的角度来看,每个处理器都使用某种机器代码,有些内部将其转换为微码(另一种机器代码),而其他一些处理器则不需要。当你听到x86 vs arm vs mips vs power pc时,这不仅是公司名字,也包括它们各自处理器的指令集,即机器代码。x86指令集虽然在不断发展,但仍类似于它们的历史,你可以很容易地从其他指令中识别出x86代码。对于所有公司来说都是如此,你可以在mips中看到mips的遗产,在arm中看到arm的遗产,等等。
所以,在处理器上运行程序时,必须将其转换为该处理器的机器代码,然后处理器才能处理它。各种语言和工具都有不同的方式处理。编译器并不需要将高级语言编译成汇编语言,但这很方便。首先,您基本上需要一个适用于该处理器的汇编器,因此该工具已经存在。其次,通过查看可读的汇编语言而不是机器码的位和字节,可以更轻松地调试编译器。一些编译器(如JAVA、Python和旧的Pascal编译器)具有通用机器码(每种语言都有自己不同的机器码),在x86和arm上运行的Java执行相同的操作,然后有一个特定目标(x86、arm、mips)的解释器,该解释器解码通用字节码并在本机处理器上执行它。但最终它必须是正在运行的处理器的机器代码。
这种编译层方法也有一些历史,我认为它是某种Unix构建块方法,一个块用于前端,另一个块用于后端和输出汇编代码,然后将汇编代码转换为目标文件是自己的工具,而与其他对象链接的对象也是自己的工具。每个块都可以包含并使用受控输入和输出进行开发,并且有时可以用适合同一位置的另一个块替换它。编译器类教授这种模型,因此您会看到这种模型在新编译器和新语言中得到复制。解析前端,即高级语言的文本。将其转换为中间的、特定于编译器的二进制代码,然后在后端将该内部代码转换为目标处理器的汇编代码,例如使用gcc等工具更改该后端,以便可以重复使用前端和中间件来针对不同的目标进行编译。然后分别拥有汇编器和链接器,它们是独立的工具。
人们一直试图重新发明键盘和鼠标,但人们已经习惯了旧方式,即使新发明更好,他们仍然坚持使用旧方法。编译器和操作系统也是如此,还有许多其他事情,我们选择我们所知道的,并且编译器通常编译为汇编语言。

0

基本上是的,Java的汇编称为字节码,任何芯片的微架构都会有一个ISA,其中包含汇编指令或类似的内容,而相同的ISA可以在许多不同的芯片上实现。如果您学习MIPS,那么这是一个很好的入门,以便您可以了解C如何通过编译器转换为MIPS。然后,您可以看到MIPS指令如何转换为机器代码,该机器代码将具有传递给ALU的操作码,以执行指令。有关更多信息,您可以阅读Hennessy / Patterson撰写的两本关于计算机硬件的好书:“计算机组成与设计”和“计算机体系结构-定量方法”


0
编译器生成本机机器代码的同时也会生成相应的汇编语言,然后再将其汇编成机器码。通常这个过程是一步完成的,但是一些编译器(如GCC)也可以输出中间的汇编代码。
你说得对,不同的架构具有不同的指令集。利用这些差异,编译器可以优化可执行文件以适应不同的处理器。

0

以下是可能让您感到困惑的一些内容:

  • 所有程序都必须转换为机器指令,因为这就是机器执行的内容。
  • 汇编语言是一种低级编程语言,几乎与机器指令一一对应。
  • 程序可以被编译成机器指令,也可以被解释为由解释器执行的机器指令。
  • 通常不会将程序转换为汇编语言,因为这需要将汇编语言转换为机器指令。我记得有一些非常古老的编译器可以生成汇编语言,但我不知道今天还有什么理由这样做。
  • 机器有多种执行机器指令的方式。它们可以是硬连线的,也可以使用微码。我怀疑几乎所有现代CPU都使用微码。这确实是一种魔法。

解释器执行的机器指令。通常术语不将解释器指令描述为机器指令。例如,在#!/bin/sh脚本中调用echo foo可以称为“机器指令”。大多数shell甚至不会预编译成字节码,但即使像CPython这样的解释器也不称它们为机器指令,因为硬件不能直接运行它们。也许是“指令”或“字节码指令”。(有一些CPU具有一些硬件辅助运行Java字节码和Lisp机器,这模糊了界限...) - Peter Cordes
我怀疑几乎所有现代CPU都使用微码 - 不是像8086或6502那样,每个指令都是由ROM编程的内部步骤序列。在现代x86 CPU中,诸如add eax,ecx之类的简单指令变成了单个内部uop,而现代RISC CPU(如ARM或AArch64)可以将它们支持的几乎每个指令作为单个内部操作运行。(这正是RISC哲学的核心点,毕竟,它允许更容易的流水线处理。)现代CPU仅在复杂指令(例如syscall)或角落情况(例如次标准FP数学)中使用微码。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接