编译的主要步骤是什么?

3

编译C程序的主要步骤是什么? 通过编译,我指的是(可能错误地)使用gcc从包含C代码的纯文本中获取二进制文件。

我想了解一些关键点:

  1. 最终,我需要将我的C代码转换为特定于CPU的语言。那么,谁关心知道我CPU特定的指令?操作系统吗?

  2. gcc是否将任何C代码转换为汇编语言?

  3. 我知道(实际上是猜测),对于每种处理器类型,我都需要一个汇编器来解释(?)汇编代码并将其转换为特定于CPU的指令。这个汇编器在哪里(由谁提供)?它随操作系统一起提供吗?

  4. 为什么如果我用文本编辑器打开二进制文件就无法看到0和1?


as - 汇编器,ld - 链接器,GCC附带这些。 - bhathiya-perera
请查看此链接:https://dev59.com/9W025IYBdhLWcg3wAxB9 - happyvirus
gcc不会直接将C语言转换为汇编代码。这里有更详细的解释:http://en.wikipedia.org/wiki/GNU_Compiler_Collection#GENERIC_and_GIMPLE - sunny1304
1
你使用的是哪个文本编辑器?如果你使用的是VIM,那么你可以输入 vim -b a.out 然后输入 :%!xxd。这将显示二进制文件的十六进制值。你也可以通过 objdump -s a.out 查看二进制文件的十六进制值。 - Z boson
1
我投票关闭此问题,因为有关编译的概念性问题应该在计算机科学SE上讨论。 - ali_m
显示剩余2条评论
4个回答

11

许多事情发生了 :)

以下是一些关键步骤(顺便说一句,这些是我认为编译的步骤,下面的步骤与标准中定义的步骤只有一点点相似)。

  1. 预处理器在源文件上运行。

    预处理器可以做很多事情,包括:

    • 它执行三字符序列的替换(特殊的三个字符序列,表示早期键盘没有的一些特殊符号)。
    • 它通过简单文本替换执行宏替换(即#define)。
    • 它获取任何头文件,并将它们的全部内容复制到#include所在的位置。

    在Linux下,执行此操作的程序是m4,使用gcc可以使用-E标志,在此步骤后停止。

  2. 预处理器运行后,我们就有了一个包含所有必要信息的文件,供解析器运行和检查语法,以及生成汇编代码。在Linux下,最可能执行此操作的程序是cc1,使用gcc可以使用-s标志,在此步骤后停止。

  3. 汇编代码由汇编器(最可能是程序gas,GNU汇编器)转换为目标代码,使用gcc可以使用-c标志在此步骤停止。

  4. 最终,一个或多个目标文件以及库被链接器转换为可执行文件。在Linux下,链接器通常是ld,使用gcc没有任何特殊标志就可以一直运行到这一步。


谢谢你的回答。我会尝试使用-C标志并查看gcc生成的汇编代码。如果你知道答案,你介意更新你的答案来回答我原始问题中列出的4个问题吗?提前感谢你。 - Pabluez
1
如果您使用-c选项编译为目标文件,将看不到任何汇编代码。您需要使用-S选项编译为汇编代码(AT&T格式)。若要输出intel格式的汇编代码,请使用-masm=intel选项。因此,如果您想要以intel格式获得汇编代码:gcc -S -masm=intel -o outfile.asm infile.c - David C. Rankin
1
传统编译器的解释很好。在-E和-S之间通常还有一个中间代码(gimple?llvm ir等),只有在使用JIT编译器时才有用。 - technosaurus
很好的答案。我添加了一些代码格式和加粗了阶段的名称,使其更容易跟随。 - nrz
1
@Pabluez 你可能想问关于连接器(linker)的问题,我不确定我能在评论中提供一个好的答案。但你可以查看John Levine的《Linkers and Loaders》,这是一个学习连接器的好起点。 - thurizas
显示剩余2条评论

6
由于您特别提到“在今天结束之前,我需要将我的C代码转换为特定的CPU应该理解的语言”,我将简要解释一下编译器的工作原理。
典型的编译器会执行以下几个步骤。
首先,它们会执行称为词法分析的操作。这一步将单个字符组合成“标记”,这些标记是下一步理解的内容。此步骤区分语言关键字(如C中的“for”和“if”)、运算符(如“+”)、常量(如整数和字符串文字)以及其他内容。它区分的具体内容取决于语言本身。
接下来是解析器,它接收由词法分析器生成的令牌流,并(通常)将其转换为称为“抽象语法树”或AST的东西。AST用数据结构表示程序所执行的计算,编译器可以浏览这些数据结构。通常,AST是与语言无关的,像GCC这样的编译器可以将不同的语言解析为一个通用的AST格式,下一步(代码生成器)可以理解。
最后,代码生成器通过AST并输出代表AST语义的代码,即实际执行AST所表示计算的代码。
对于GCC和其他编译器,编译器实际上不会生成机器代码。相反,它输出汇编代码,然后将其传递给汇编器。汇编器通过类似词法分析、解析和代码生成的过程,实际上生成机器代码。毕竟,汇编器只是将汇编代码编译成机器代码的编译器。
对于C(以及许多其他语言),汇编器通常不是最后一步。汇编器会生成称为目标文件的东西,其中包含对其他目标文件或库中函数的未解决引用(如C标准库中的printf或项目中其他C文件中的函数)。这些目标文件被传递给称为“链接器”的东西,它的工作是将所有目标文件组合成一个单独的二进制文件,并解决目标文件中的所有未解决引用。
最后,在所有这些步骤之后,您将获得完整的可执行二进制文件。
请注意,这是GCC和许多其他编译器的工作方式,但不一定是所有情况。任何您编写的程序,只要它准确接受C代码流并输出与之等效的某些其他代码流(汇编、机器代码甚至javascript),都是编译器。
此外,这些步骤并不总是完全分离的。编译器可能不会完全分析整个文件,而是在有一些令牌时开始解析,然后在解析器需要更多令牌时返回到词法分析。当解析器感觉已经了解足够多的内容时,它可能会在词法分析器为其生成更多令牌之前执行一些代码生成。

1
好的讨论。唯一缺少的是链接器正在创建什么。简要讨论ELF格式(以及竞争格式)会很有益处可执行和可链接格式(ELF)。这将使其相当完整。 - David C. Rankin
1
@Pabluez 当然可以。虽然可能会很丑陋,但你理论上可以用bash编写一个bash到汇编的编译器。 - jack_rabbit
1
@Pabluez 关于为什么您看不到0和1,是因为您的编辑器将字节解释为字符。如果您尝试打开二进制文件,可能会看到一些疯狂的字形。如果您想接近(十六进制与二进制一样好),请尝试 $ hexedit my_executable - jack_rabbit
1
@Pabluez 如果你打开一个文件,然后写一堆1和0,你并不是字面上把这个1位和0位的序列写入文件,而是为每个“1”和“0”编码成一个字符编码(可能是ASCII),写入字节(可能是8位)。 - jack_rabbit
1
你可以可能编写一个文本编辑器,能够读取和编辑二进制,实际上,我确定有许多这样的编辑器存在,但它们相对无用,因为长串的二进制对人类来说极难阅读。 - jack_rabbit
显示剩余5条评论

2
今天结束时,我需要将我的C代码转换为一种特定于我的CPU的语言。那么,谁关心知道我的CPU特定的指令?操作系统吗?
这里不太清楚。如果您正在询问哪个工具具有CPU特定指令的知识,那么它是汇编器、反汇编器、调试器,以及可能还有其他一些工具。它们可以生成机器码或将其转换回反汇编。
如果您想知道谁关心使用哪些指令,那么需要执行它们的处理器,因为每个指令集都以完全不同的方式表示即使是如“将两个整数相加”的常见指令。
gcc是否将任何C转换为汇编语言?
是的,GCC将C(或任何其他支持的语言中的程序)转换为汇编语言。此过程涉及许多步骤,并且至少使用了两个附加的内部表示。详细信息在GCC internals文档中解释。最后,编译器“后端”通过前面编译器传递生成的简单“模式”生成汇编表示。您可以使用-S标志要求GCC输出此汇编。如果您没有明确要求,下一步(汇编)将自动执行,您只会看到最终的可执行文件。
我知道(实际上是猜测),对于每种处理器类型,我都需要一个汇编器来解释汇编代码并将其转换为特定于我的CPU的指令。这个汇编器在哪里(由谁提供)?它随操作系统一起提供吗?
首先要注意的是,每个CPU的汇编语言都不同,因为它们应该是CPU的机器语言1:1的表示。然后,汇编器将汇编代码转换为机器码。谁提供它?任何构建它的人都可以提供。使用GNU工具链,它是binutils包的一部分,并且通常默认安装在大多数Linux发行版中。这不是唯一可用的汇编器。还要注意,虽然GNU“套件”(GCC/binutils/gdb)支持许多体系结构,但您需要使用适合您体系结构的端口。例如,您桌面PC的默认汇编器无法编译/汇编为ARM机器码。
为什么如果我用文本编辑器打开二进制文件,就不能看到0和1呢?
因为文本编辑器应该显示0和1的文本表示。假设文件中的每个字符占8位,它们将每个后续的8位解释为单个字符,而不是显示单独的位。如果您知道在标准的8位ASCII字母中,字母'A'由值65表示,您也可以将其转换回二进制:01000001。将十六进制表示法转换回二进制要容易一些。为此,您可以使用hexdump(或类似)工具。

非常好的回答。您所说的每个CPU都有一种汇编语言,是指架构吗?因为我可以下载同一个程序的相同二进制文件,并且它将在编译代码的架构的任何处理器上运行,对吗? - Pabluez
另外一件事:其他同事说汇编转换是一种选择,但GCC有工具可以直接将C源代码转换为目标文件供链接器(ld)使用。这是什么意思?这是真的吗? - Pabluez
1
多少有点...在x86架构中有许多CPU,但每个CPU代际都会添加新指令。因此,并非所有架构内的CPU都兼容。据我所知,GCC后端始终在内部创建汇编代码,然后“编译器驱动程序”调用汇编器进行汇编并创建对象文件。如果您想使用GCC,您将始终安装binutils,因此这不是问题。其他编译器可能直接生成机器代码。 - dbrank0

1

在一天结束前,我需要将我的C代码转换为一种特定于我的CPU的语言。那么,谁在乎知道我的CPU特定的指令?操作系统吗?

CPU。

但请注意,在现代计算机上,表面上单个的CPU只是一种错觉。

不过,对于简单的C编程来说,这已经是一个足够好的概念模型了。


如果你要求的话,gcc会将任何C语言转换为汇编语言。选项-S将生成一个汇编清单。对于PC,您可以选择AT&T语法或普通的Intel语法。不幸的是,AT&T(通过-masm=att确定)是默认的,但您可以使用-masm=intel来获取普通的汇编语言。 如果你没有要求生成汇编代码,那么gcc可能会直接从其内部抽象语法树(AST)生成目标代码。 将汇编语言作为中间形式产生只会增加复杂性和低效率,所以我非常怀疑它是否这样做。
我知道(实际上是猜测)对于每种处理器类型,我都需要一个汇编器来解释汇编代码并将其转换为特定于我的CPU的指令。这个汇编器在哪里(由谁提供)?它随操作系统一起提供吗?
你不需要这样的汇编器。但是gcc带有一个汇编器as。类Unix操作系统通常捆绑了gccas,而Windows没有捆绑开发工具。然而,微软的开发工具现在可以免费下载,包括完整的Visual Studio IDE。微软的汇编器是ml.exe,被称为MASM,宏汇编器(好像没有其他宏汇编器一样)。
“为什么我用文本编辑器打开二进制文件看不到0和1?”这取决于文本编辑器。虽然我不知道有哪个文本编辑器可以显示0和1,但文本编辑器是设计用来将字节解释为文本的。如果您需要这样的文本编辑器,您可以自己编写。但请注意:我无法想出任何实际用途。
关于标题中的问题,主要步骤如下:
实际上有两个主要步骤:编译和链接。编译步骤进一步细分为预处理和核心语言编译,即
编译 → 链接
实际上是
(预处理 → 核心语言编译)→ 链接
在预处理期间,源代码文件通过#include指令组合在一起。这会生成一个完整的源代码“翻译单元”。核心语言编译将其转换为包含某些未解决引用的机器码的目标代码文件。
最后,链接步骤将目标代码文件(包括库中的目标代码文件内容)组合成一个完整的可执行文件。

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