gcc实际上生成汇编代码并使用as汇编器进行汇编。并非所有编译器都这样做 - MS编译器直接生成目标代码,尽管您可以让它们生成汇编输出。将汇编代码转换为目标代码是一个相当简单的过程,至少与C→Assembly或C→Machine-code翻译相比如此。
一些编译器会产生其他高级语言代码作为其输出 - 例如,第一个C++编译器cfront生成C作为其输出,然后由C编译器将其编译为机器码。
请注意,直接编译或汇编实际上并不会生成可执行文件。这是由链接器完成的,它获取编译/汇编产生的各种目标代码文件,解析它们包含的所有名称,并生成最终的可执行二进制文件。
几乎所有编译器(包括gcc)都会生成汇编代码,因为这样做更容易——编写和调试编译器。主要的例外通常是即时编译器或交互式编译器,它们的作者不想承担性能开销或分叉整个进程以运行汇编程序的麻烦。一些有趣的例子包括
这些情况的共同点是对“即时”响应的需求。汇编器和链接器足够快,但对于交互响应来说还不够好。但是,技术正在不断进步。
还有一大类语言,例如Smalltalk、Java和Lua,它们编译为字节码而不是汇编代码,但其实现可能随后直接将该字节码转换为机器代码,而无需使用汇编器。
(注:在1990年代初,玛丽·费尔南德斯和我编写了新泽西机器码工具包,其代码在线上,可生成C库,编译器编写人员可以使用它来绕过标准汇编程序和链接程序。 玛丽用它将她的优化链接程序生成 a.out
的速度大约提高了一倍。 如果不写入磁盘,加速甚至更快……)gcc -v
),以获取它执行的命令列表,查看它在幕后做了什么。gcc
在内部确实编译成一个临时的 .s
汇编文件,并在其上运行 as
。 -S
选项只是停在那里。 另一方面,MSVC 通常只输出一个 .obj
文件,它的汇编输出选项会生成一个巨大的臃肿的 .asm
文件(其中包含你从未调用过的模板或库函数的定义),有时需要将其修剪以正确地进行汇编和链接,以避免重复符号错误。 GCC 在正常操作期间确实以非常真实的方式编译为汇编语言,而 MSVC 则不会。(ICC 或 clang/LLVM 也不会,但它们可以输出与其 .o 文件匹配的汇编语言)。 - Peter CordesGCC编译成汇编语言。其他一些编译器则不会。例如,LLVM-GCC编译为LLVM汇编或LLVM字节码,然后再编译成机器代码。几乎所有编译器都有某种形式的内部表示,LLVM-GCC使用LLVM,而GCC使用称为GIMPLE的东西。
cc1
内部的非文本数据结构表示。同样地,对于LLVM-IR来说,它可能从未序列化为字节码,更不用说文本了,只是在clang前端和LLVM后端及其优化器传递的数据结构之间传递。我听说过LLVM-GCC,但不知道它是如何工作的。我猜你是说它输出一个.ll
的LLVM-IR,并在其上运行llvm-as进行优化,生成一个.o
。 - Peter Cordes编译器通常会将源代码解析成抽象语法树(AST),然后再转换成一些中间语言。通常在进行一些优化后,它们才会生成目标语言。
关于gcc,它可以编译到各种不同的目标平台。我不知道它是否首先将x86编译成汇编语言,但是我已经给你提供了一些有关编译器的见解 - 你也要求这样做。
没有一个答案阐明了汇编语言是在二进制代码和机器相关符号代码之间的第一层抽象。编译器是在机器相关符号代码和机器无关符号代码之间的第二层抽象。
如果编译器直接将代码转换为二进制代码,按定义,它将被称为汇编器而不是编译器。
更合适的说法是编译器使用中间代码,可能是汇编语言,也可能不是汇编语言,例如Java使用字节码作为中间代码,而字节码是Java虚拟机(JVM)的汇编语言。
编辑:您可能会想知道为什么汇编器总是产生机器相关代码,而编译器能够产生机器无关代码。答案很简单。汇编器是机器代码的直接映射,因此它生成的汇编语言始终与机器有关。相反,我们可以为不同的机器编写多个版本的编译器。因此,要使我们的代码独立于机器运行,我们必须在为该机器编写的编译器版本上编译相同的代码。
它通过以下4个步骤实现:
/usr/lib/gcc/x86_64-pc-linux-gnu/10.1.0/cc1
或cc1plus
完成。这种情况已经持续了很多年。几十年前,CPP是一个单独的步骤,它生成一个临时文件,但现在不再是这样了。然后,通常使用GNU Binutils中的as
将asm->object文件完成(这是一个与GCC分开维护的软件包),然后使用ld
进行链接(也来自Binutils)。 - Peter Cordesgcc
前端将源代码转换为链接可执行文件没有任何关系。 - Peter Cordes编译有许多阶段。抽象地说,有前端读取源代码,将其分解为标记,最终形成语法树。
后端负责首先生成类似三地址代码的顺序代码:
代码:
x = y + z + w
转换为:
reg1 = y + z
x = reg1 + w
然后对其进行优化,将其翻译成汇编语言,最终转换为机器语言。所有步骤都被仔细分层,以便在需要时可以替换其中之一。