编译背后的主要步骤是什么?
编译C程序的主要步骤是什么? 通过编译,我的意思是(可能是错误的)使用gcc从包含C代码的纯文本中获取二进制文件。
我很想了解这个过程的一些关键点:
-
到那天结束时,我需要将我的C代码转换为我的CPU应该理解的语言。 那么,谁在乎了解我的CPU特定指令呢? 操作系统?
-
gcc是否将任何C转换为汇编语言?
-
我知道(实际猜测)对于每种处理器类型,我将需要一个汇编程序来解释(?)汇编代码并转换为我的CPU特定指令。 这个汇编程序(谁发货)在哪里? 它是否附带操作系统?
-
如果我用文本编辑器打开二进制文件,为什么我看不到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(或类似)工具。
发生了很多:)
以下是一些关键步骤(顺便说一句,这些是我对编译的看法,以下步骤只与标准中定义的步骤有相似之处)。
-
预处理器在源文件上运行。
预处理器为我们做了各种各样的事情,包括:
- 它执行三字形 (特殊的三个字符序列,代表早期键盘没有的一些特殊符号) 替换 。
- 它通过简单的文本替换执行宏替换 (即
#define
) - 它抓取任何头文件并将其全部内容复制到
#include
行所在的位置。
在Linux下,执行此操作的程序是
m4
,使用gcc
可以在此步骤之后使用-E
标志停止。 -
在预处理器运行之后,我们有一个文件,其中包含解析器运行和检查语法所需的所有信息,并发出汇编 。 在Linux下,最有可能执行此操作的程序是
cc1
,使用gcc
可以在此步骤后使用-s
标志停止。 -
程序集
gas
(GNU Assembler)很可能将程序集转换为目标代码 ,使用gcc
可以使用-c
标志在此步骤中停止。 -
最后, 链接器将一个或多个目标文件以及库转换为可执行文件 。 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
指令进行组合。 这产生了源代码的完整翻译单元 。 核心语言编译将其转换为目标代码文件 ,其中包含具有一些未解析引用的机器代码。
最后,链接步骤将目标代码文件(包括库中的目标代码文件内容)组合在一起,以创建单个完整的可执行文件。