12

来自C++ 中的思考 - 第 1 卷

在第二遍中,代码生成器遍历分析树并为树的节点生成汇编语言代码机器代码。

至少在 GCC 中,如果我们提供生成汇编代码的选项,编译器会通过创建包含汇编代码的文件来服从。但是,当我们简单地运行gcc没有任何选项的命令时,它不会在内部生成汇编代码吗?

如果是,那为什么需要先生成汇编代码,然后再翻译成机器语言?

4

4 回答 4

13

TL:DR 不同的目标文件格式/更容易移植到新的 Unix 平台(历史上)是 gcc 将汇编器与编译器分开的主要原因之一,我认为。 在 gcc 之外,主流的 x86 C 和 C++ 编译器(clang/LLVM、MSVC、ICC)直接进入机器代码,如果您要求它们,可以选择打印 asm 文本

LLVM 和 MSVC 带有完整的工具链,而不仅仅是编译器。(还带有汇编器和链接器)。LLVM 已经将目标文件处理作为库函数,因此它可以使用它而不是写出 asm 文本来提供给单独的程序。

较小的项目通常选择将目标文件格式的详细信息留给汇编器。例如,FreePascal 可以直接转到它的一些目标平台上的目标文件,否则只能转到 asm。有许多说法(1234)几乎所有的编译器都通过 asm 文本,但对于许多拥有大量开发人员的最大最广泛使用的编译器(GCC 除外)来说,情况并非如此。

C 编译器倾向于仅针对单个平台(例如供应商的微控制器编译器)并被编写为“该平台的 C 实现”,或者是非常大的项目,例如 LLVM,其中不包括机器代码生成编译器自己的代码大小的很大一部分。不太广泛使用的语言的编译器通常更可移植,但不想编写自己的机器代码/目标文件处理。(现在很多编译器都是 LLVM 的前端,所以可以.o免费获得输出,比如rustc,但是旧的编译器没有这个选项。)

在所有编译器中,大多数都使用 asm。但是,如果您按每天使用每个文件的频率来衡量,那么直接访问可重定位目标文件 ( .o/ .obj) 占全球任何一天完成的总构建量的很大一部分。即,如果您正在阅读本文,您关心的编译器可能会以这种方式工作。

此外,像javac这样针对可移植字节码格式的编译器使用 asm 的理由较少;相同的输出文件和字节码格式适用于它们必须运行的每个平台。

有关的:


为什么 GCC 做它做的事

是的,as是一个单独的程序,gcc前端实际上与cc1(生成文本 asm 的 C 预处理器 + 编译器)分开运行。

这使得 gcc 更加模块化,使编译器本身成为一个文本 -> 文本程序。

GCC 在内部使用一些二进制数据结构来表示 GIMPLE 和 RTL 内部表示,但它不会将这些 IR 格式(的文本表示)写入文件,除非您使用特殊选项进行调试。

那么为什么要在组装时停下来呢?这意味着 GCC 不需要知道同一目标的不同目标文件格式。 例如,不同的 x86-64 操作系统使用 ELF、PE/COFF、MachO64 目标文件和历史上的 a.out。 as将相同的文本汇编组装成相同的机器代码,由不同目标上的不同目标文件元数据包围。(gcc 必须知道一些细微的区别,比如是否在_符号名称前加上一个,是否可以使用 32 位绝对地址,以及代码是否必须是 PIC。)

任何特定于平台的怪癖都可以留给 GNU binutils as(又名 GAS),或者 gcc 可以使用系统随附的供应商提供的汇编程序。

从历史上看,有许多不同的 Unix 系统具有不同的 CPU,或者特别是相同的 CPU,但它们的目标文件格式有不同的怪癖。更重要的是,一组相当兼容的汇编指令,如.globl main,.asciiz "Hello World!\n"和类似指令。GAS 语法来自 Unix 汇编程序。

过去确实可以将 GCC 移植到新的 Unix 平台而无需移植as,只需使用操作系统附带的汇编程序即可。

从来没有人将汇编器作为库集成到 GCC 的cc1编译器中。这是为 C 预处理器完成的(历史上也是在一个单独的进程中完成的),但不是汇编器。


大多数其他编译器确实直接从编译器生成目标文件,没有文本 asm 临时文件/管道。通常是因为编译器仅针对一个或几个目标设计,例如 MSVC 或 ICC 或最初仅作为 x86 的各种编译器,或许多供应商提供的嵌入式芯片编译器。

clang/LLVM 的设计比 GCC 晚得多。它被设计为优化 JIT 后端,因此它需要一个内置的汇编程序来快速生成机器代码。作为一个提前编译器,添加对不同目标文件格式的支持可能是一件小事,因为内部软件架构可以直接生成二进制机器代码。

LLVM 当然在寻找特定于后端的优化之前在内部使用 LLVM-IR 进行与目标无关的优化,但同样,如果您要求它,它只会将此格式写为文本。


于 2018-12-17T15:21:54.760 回答
4

汇编阶段的合理性有两个原因:

  • 它允许将 c/c++ 代码转换为与机器无关的抽象汇编器,从中可以轻松转换为多种不同的指令集架构
  • 当可以利用现有软件 [组件] 时,它消除了验证 CISC 架构的正确操作码、前缀、r/m 等指令编码的负担。

那本书的第一版是从 2000 年开始的,但不妨谈谈 90 年代初期,当时 c++ 本身被翻译成 c,当时 gnu/自由软件的想法(包括编译器的源代码)还不为人所知。

编辑: GCC 使用的几种无意义的抽象机器独立语言之一是 RTL -注册传输语言

于 2012-12-26T12:20:46.207 回答
3

这是编译器实现的问题。汇编代码是高级语言(正在编译的语言)和生成的二进制输出之间的中间步骤。一般来说,首先转换为汇编,然后转换为二进制代码比直接创建二进制代码更容易。

于 2012-12-26T11:23:38.180 回答
2

Gcc 确实将汇编代码创建为临时文件,调用汇编器,可能还有链接器,具体取决于您在命令行上执行或不添加的操作。这会创建一个对象,然后如果启用二进制文件,则所有临时文件都会被清理。使用 -save-temps 查看实际情况(有许多临时文件)。

不带任何选项运行 gcc 绝对会创建一个 asm 文件。

这没有“需要”,这只是他们碰巧设计它的方式。我假设出于多种原因,您在开始编译器之前已经想要/需要一个汇编器和链接器(先于马车,在处理器上先于其他语言使用 asm)。“unix 方式”是不重新发明工具或库,而只是在顶部添加一点,这意味着要使用 asm,然后让汇编器和链接器完成其余的工作。您不必以这种方式重新发明这么多的组装工作(多次通过、解析标签等)。开发人员调试 ascii asm 比调试位更容易。几代编译器都在这样做。及时编译器是这种习惯的主要例外,根据定义,它们必须能够转到机器代码,所以它们可以或可以。

于 2012-12-26T13:18:15.423 回答