编译背后的主要步骤是什么?

时间:2014-11-20 03:24:40

标签: c gcc assembly compiler-construction compilation

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

我很想了解这个过程的一些关键点:

  1. 到一天结束时,我需要将我的C代码转换为我的CPU应该理解的语言。那么,谁在乎了解我的 CPU特定说明?操作系统?

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

  3. 我知道(实际猜测)对于每种处理器类型,我需要一个汇编程序,它将解释(?)汇编代码并转换为我的CPU特定指令。这个汇编程序(谁发货)在哪里?它是否附带操作系统?

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

4 个答案:

答案 0 :(得分:9)

很多事情发生:)

以下是一些关键步骤(顺便说一句,这些是我对编译的看法,以下步骤只与标准中定义的步骤有相似之处)。

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

    预处理器为我们做了各种各样的事情,包括:

    • 执行三字形(特殊的三个字符序列代表早期键盘没有的一些特殊符号)替换
    • 通过简单的文字替换执行宏替换(即#define
    • 它会抓取所有标题文件,并将其全部内容复制到#include行所在的位置。

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

  2. 预处理器运行后,我们有一个文件,其中包含解析器运行和检查语法所需的所有信息,并发出程序集。在Linux下,最有可能执行此操作的程序为cc1,使用gcc后,您可以使用-s标记在此步骤后停止。

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

  4. 最后,链接器将一个或多个目标文件以及库转换为可执行文件。 Linux下的链接器通常为ld,使用gcc时没有任何特殊标志会一直运行。

答案 1 :(得分:6)

由于您特别提到'到一天结束时我需要将我的C代码转换为我的CPU应该理解的语言',我将解释一下编译器的工作原理。

典型的编译器做了一些事情。

首先,他们做了一些叫做lexing的事情。这个步骤采用单个字符并将它们组合成“令牌”,这是下一步所理解的事物。此步骤区分语言关键字(如C中的'for'和'if'),运算符(如'+'),常量(如整数和字符串文字)以及其他内容。它的区别究竟取决于语言本身。

下一步是解析器,它获取词法分析器产生的令牌流,并(通常)将其转换为称为“抽象语法树”或AST的东西。 AST表示程序使用编译器可以导航的数据结构进行的计算。通常,AST是与语言无关的,像GCC这样的编译器可以将不同的语言解析为下一步(代码生成器)可以理解的常见AST格式。

最后,代码生成器通过AST并输出代表AST语义的代码,即实际执行AST所代表的计算的代码。

对于GCC,可能还有其他编译器,编译器实际上并不生成机器代码。相反,它输出传递给汇编程序的汇编代码。汇编程序经历了类似的lexing,解析和代码生成过程,以实际生成机器代码。毕竟,汇编程序只是一个编译汇编代码的编译器。

在C(和许多其他人)的情况下,汇编程序通常不是最后一步。汇编程序生成称为目标文件的东西,其中包含对其他目标文件或库中函数的未解析引用(如C标准库中的printf或项目中其他C文件的函数)。这些目标文件被传递给称为“链接器”的东西,它的作用是将所有目标文件组合成一个二进制文件,并解析目标文件中所有未解析的引用。

最后,在完成所有这些步骤之后,您将拥有完整的可执行二进制文件。

请注意,这是GCC和许多其他编译器工作的方式,但并不一定如此。您可以编写的任何程序都准确地接受C代码流并输出一些等效的其他代码(程序集,机器代码,甚至是javascript)的流程,这是一个编译器。

此外,这些步骤并非总是完全分开。而不是lexing和整个文件,然后解析整个结果,然后为整个AST生成代码,编译器可能会做一些lexing,然后在它有一些令牌时开始解析,然后当解析器需要更多令牌时再回到lexing 。当解析器感觉它足够了解时,它可能会在lexer为它生成更多标记之前进行一些代码生成。

答案 2 :(得分:2)

  

到那天结束时,我需要将我的C代码转换为我的CPU应该理解的语言。那么,谁在乎了解我的CPU特定指令呢?操作系统?

你在这里不是很清楚。如果您在询问哪个工具了解您的CPU特定指令,那么它就是汇编器,反汇编器,调试器以及其他一些工具。他们可以生成机器代码或将其转换回反汇编。

如果你问谁在乎使用哪些指令,那就是需要执行它们的处理器,因为每个指令集甚至代表了以完全不同的方式“添加两个整数”这样的通用指令。

  

gcc是否将任何C转换为汇编语言?

是的,C(或任何其他支持语言的程序)由GCC转换为汇编。涉及许多步骤,并且在过程中使用至少两个额外的内部表示。详细信息在GCC internals文档中进行了解释。最后,编译器“后端”生成由先前编译器传递生成的简单“模式”的汇编表示。您可以通过使用-S标志要求GCC输出此程序集。如果您没有特别要求,则自动执行下一步(组装),您只能看到最终的可执行文件。

  

我知道(实际猜测)对于每种处理器类型,我需要一个汇编程序来解释(?)汇编代码并转换为我的CPU特定指令。这个汇编程序(谁发货)在哪里?它是否附带操作系统?

首先请注意,每个CPU的汇编语言不同,因为它们应该代表CPU的机器语言1:1。汇编程序然后将汇编代码转换为机器代码。是谁发货的?建造它的任何人。使用GNU工具链,它是binutils包的一部分,它通常默认安装在大多数Linux发行版上。这不仅是汇编程序。另请注意,虽然GNU“suite”(GCC / binutils / gdb)支持许多体系结构,但您需要为您的体系结构使用适当的端口。例如,桌面PC的默认汇编程序无法编译/汇编为ARM机器代码。

  

如果用文本编辑器打开二进制文件,为什么我看不到0和1?

因为文本编辑器应该显示该0和1的文本表示。假设文件中的每个字符占用8位,它们将每个后续8位解释为单个字符,而不是显示单独的位。如果你知道在标准的8位ASCII字母'A'用值65表示,你也可以将它转换回二进制:01000001.将十六进制表示转换回二进制更容易一些。为此,您可以使用hexdump(或类似)工具。

答案 3 :(得分:1)

  

到一天结束时,我需要将我的C代码转换为我的CPU应该理解的语言。那么,谁在乎了解我的CPU特定指令呢?操作系统?

CPU。

但请注意,在现代计算机上,显然单CPU只是一种幻觉。

这对于简单的C编程来说是一个很好的概念模型。


  

gcc是否将任何C转换为汇编语言?

如果你问它。选项-S将生成程序集列表。对于PC,你可以选择AT& T语法,这种语法很丑陋,有百分号,还有普通的英特尔语法。不幸的是,AT& T(可通过-masm=att IIRC选择)是默认设置,但您可以使用-masm=intel来进行普通组装。

如果你不要求它生成程序集,那么gcc可能直接从其内部抽象语法树(AST)生成目标代码。

生成汇编语言作为中间形式只会增加复杂性和低效率,所以我非常怀疑它是否会这样做。


  

我知道(实际猜测)对于每种处理器类型,我需要一个汇编程序来解释(?)汇编代码并转换为我的CPU特定指令。这个汇编程序(谁发货)在哪里?它是否附带操作系统?

您不需要这样的汇编程序。但是gcc附带了汇编程序as。类Unix操作系统通常捆绑gccas,而Windows没有捆绑的开发人员工具。然而,微软的开发工具可以免费下载,现在(在过去一周左右),包括完整的Visual Studio IDE。 Microsoft的汇编程序是ml.exe,称为MASM,宏汇编程序(就好像没有其他宏汇编程序一样)。


  

如果我用文本编辑器打开二进制文件,为什么我看不到0和1?

这取决于文本编辑器,虽然我不知道任何可以呈现0和1;文本编辑器旨在将字节解释为文本。

如果需要,您可以编写这样的文本编辑器。

虽然公平警告:它没有我能想到的实际用途。


最后关于标题中的问题,

  

编译背后的主要步骤是什么?

在实践中,有两个主要步骤:编译链接。编译步骤进一步细分为预处理核心语言编译,即

编译→链接

......真的是

(预处理→核心语言编译)→链接

在预处理期间,源代码文件通过#include指令组合。这会生成完整的翻译单元源代码。核心语言编译将其转换为目标代码文件,其中包含带有一些未解析引用的机器代码。

然后最后,链接步骤将目标代码文件(包括库中的目标代码文件内容)组合在一起,以创建一个完整的可执行文件。