目录
一、GCC编译器
二、编译过程
1、预处理(Preprocessing)
2、编译(Compilation)
3、汇编(Assembly)
4、链接(Linking)
三、秋招真题演练
一、GCC编译器
在这里,先给大家简单介绍一下GCC编译器,相信对gcc并不陌生。GCC(GNU Compiler Collection,GNU编译器套件)是由 GNU 开发的编程语言译器。 GNU编译器套件包括C、C++、 Objective-C、 Java、 Ada 和 Go语言前端,也包括了这些语言的库(如 libstdc++,libgcj等)。
相信大家平时也没少使用gcc编译器,但是却很少有人去研究gcc的编译流程。简单来说,编程语言分为机器语言、汇编语言、高级语言。而我们常使用的C语言属于高级语言,gcc编译器就是将我们的C语言源程序变为汇编语言,最终变成计算机可识别的机器语言。
接下来叫我们一起来具体看看C程序的编译过程吧!
二、编译过程
一个C语言从源程序到可执行代码分成四个步骤,分别是:预处理、编译、汇编、链接。先给大家看一下流程图,然后我们在一条一条进行学习。
接下来,叫我们一起来深入探讨一下吧!
1、预处理(Preprocessing)
预处理是读取c源程序,经过处理,生成一个.i文件,该文件的含义与源文件是相同的,但是没有宏定义、没有条件编译指令、没有特殊符号的输出文件。
.i文件是如何生成的呢?这就需要我们来看一下预处理都干了哪些事吧!
(1)删除所有的注释
注释不属于程序代码,它们是为了方便人理解代码,对程序的运行没有特别作用。
(2)宏扩展
将所有的#define删除,并展开所有的宏定义
(3)条件预编译指令
处理所有的条件预编译指令,比如#if、#ifdef、#elif、#else、#endif等
(4)文件包含
在预处理期间包含文件会导致在源代码中添加文件名的全部内容,从而替换 #include<文件名> 指令。
在预处理前,我先编写一个C源程序hello.c,代码如下:
/*hello.c源文件*/
#include <stdio.h>
#define TIMES 5
int main(int argc,char *argv[])
{
int i;
for(i=0; i<TIMES; i++)
{
printf("Hello world\n");
}
return 0;
}
通常使用如下预处理指令:
gcc -E hello.c -o hello.i
经过预处理后,得到hello.i文件,该文件还是C语言源代码,我们可以进行查看,大家可以试试,因为比较长,这里就给出部分截图了。
2、编译(Compilation)
编译程序就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
通常使用如下命令进行编译生成汇编文件。
gcc -S hello.i -o hello.s
经过编译处理后,得到一个hello.s文件,当我们查看查看该文件时,我们发现.s文件已经是一个汇编语言编写的文件了,没有学过汇编语言的同学已经看不懂了吧(由于代码较长,下面只给部分截图进行参考)
3、汇编(Assembly)
使用汇编程序将程序集代码(.s文件)转换为机器可理解的代码(二进制/十六进制形式)。汇编程序是一个预先编写的程序,它将汇编代码转换为机器代码。它从程序集代码文件中获取基本指令,并将其转换为特定于计算机类型(成为目标代码)的二进制/十六进制代码。
目标文件与程序集文件同名,存放的是与源程序等效的目标的机器语言代码。在UNIX操作系统中为.o文件。目标文件由段组成,通常一个目标文件中至少有两个段:
(1)代码段:该段所包含的主要是程序的指令,该段一般是可读和可执行的,但一般不可写。
(2)数据段:主要存放程序中要用到的各种常量、全局变量、静态的数据。一般数据段都是可读,可写,可执行的。
通常可以使用如下命令进行汇编操作
gcc -c hello.s -o hello.o
经过汇编操作,我们得到了hello.o文件。经过查看,我们发现已经是一堆乱码,但其实这才是机器可以识别的二进制文件。
4、链接(Linking)
经过汇编操作,文件已经成为了计算机可识别的二进制文件,那我们可以立即执行吗?让我们实践操作一下。
为什么会出现这种情况呢?其实啊,我们还有很多问题没有解决,比如说,我们在该源文件中调用了printf函数,但是目前文件中并没有该函数的定义。此时就需要链接来解决该问题了,链接的主要任务就是将有关的目标文件彼此相连接,使得所有的目标文件成为一个能够被操作系统装入执行的统一整体,也就是可执行程序。
根据库函数的链接方式的不同,链接处理可分为两种:
(1) 静态链接
静态链接是指在编译阶段直接把静态库加入到可执行文件中去(.o文件与链接库.a文件拼接在一起),如图所示:
通常我们可以使用如下命令,利用静态链接生成可执行文件:
gcc hello.o -o hello -static
(2)动态链接
动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去,如图所示:
通常我们可以使用如下命令,利用动态链接生成可执行文件:
gcc hello.o -o hello
接下来我们来看一看静态链接和动态链接有什么区别呢?
我们可以使用如下命令查看静态链接生成的可执行文件和动态链接生成的可执行文件所占内存大小:
du -h hello
查看后,分别为:
静态链接:
动态链接:
我们可以看到,静态链接生成的可执行文件所占内存远远大于动态链接所生成的可执行文件。
除此之外,我们还要知道一点二者的区别:静态链接在编译期完成,动态链接在运行期完成。
最后,在经过了预处理、编译、汇编、链接后,我们使用如下命令执行可执行文件,来看看我们源文件的编译是否成功:
./hello
三、秋招真题演练
(该题为牛客网小米公司2022年嵌入式工程师面试题第2题)
题目:C编译到执行的4个阶段
参考答案:
在C语言编译运行的过程中,可以分为4个主要的阶段,包括预处理、编译、汇编和链接。
预处理阶段(Preprocessing):在预处理阶段,编译器会处理源文件,包括展开宏定义、头文件的展开、条件编译等,生成一个经过预处理后的文本文件。此阶段的结果是一个以 .i 为扩展名的文件。
编译阶段(Compilation):在编译阶段,编译器将经过预处理的文本文件翻译成汇编代码。汇编代码是一种低级的、与机器相关的语言。此阶段的结果是一个以 .s 为扩展名的文件。
汇编阶段(Assembly):在汇编阶段,汇编器将汇编代码转换成机器可以执行的指令。此阶段的结果是一个以 .o 为扩展名的文件。
链接阶段(Linking):在链接阶段,连接器将目标文件以及一些必要的库文件进行链接,生成可执行文件。此阶段的结果是一个没有扩展名的可执行文件。
以上是C语言编译执行的基本阶段,具体的实现方式可能因编译器和操作系统的不同而有所不同。