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

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

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

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

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

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

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

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

你在这里不是很清楚。 如果你问,哪个工具知道你的CPU特定指令,它是汇编程序,反汇编程序,调试程序,也许还有其他一些。 他们可以生成机器代码或将其转换回反汇编。

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

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

是的,C(或任何其他支持语言的程序)由GCC转换为汇编。 涉及许多步骤,并且在处理中使用至少两个额外的内部表示。 细节在GCC内部文件中解释。 最后,编译器“后端”生成由先前编译器传递生成的简单“模式”的汇编表示。 您可以通过使用-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.将hex表示转换回二进制更容易一些。 为此,您可以使用hexdump(或类似)工具。

发生了很多:)

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

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

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

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

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

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

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

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

既然你特别提到’到一天结束时我需要将我的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 。 当解析器感觉它足够了解时,它可以在让词法分析器为它产生更多标记之前进行一些代码生成。

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

CPU。

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

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


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

如果你问它。 选项-S将生成assembly清单。 对于PC,您可以选择AT&T语法,这是一种丑陋的罪恶,百分号和普通的英特尔语法。 不幸的是,AT&T(可通过-masm=att IIRC选择)是默认设置,但您可以使用-masm=intel来获得普通程序集。

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

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


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

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


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

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

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

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


最后关于标题中的问题,

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

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

编译→链接

… 是真的

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

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

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