C语言编译

这一篇文章我们来聊一聊C语言的编译过程,就是从 .c 文件到 .exe 文件的过程。

关于C语言的编译过程,对很多人来说都感到特别苦涩,但是理解了这个过程对大家以后的能力上的提升是很有帮助的,在这里我会努力用最浅显有趣的语言来为大家解读,还请大家认真仔细品读,辛苦诸位喽!

C语言的编译过程分为下列四步:

  1. 预处理 --> .c文件到 .i文件的过程
  2. 编译、优化 --> .i文件到 .s文件的过程
  3. 汇编 --> .s文件到 .o文件的过程
  4. 链接 --> .o文件到 .exe可执行文件的过程

首先我们先用一张图来看一下这个流程

C语言的编译过程大体就是上面这个样子了,下面我们来慢慢分析这四个步骤吧!

1、预处理

读取C源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理

【注:伪指令主要包含下列四个方面】

①宏定义指令(如#define NAME TokenString、#undef等)

对于前一个伪指令(#define NAME TokenString)预编译所要做的是将程序中的所有NAME用TokenString替换,但作为字符串常量的NAME则不被替换。对于第二个伪指令(#undef)则是将取消对某个宏的定义,使以后该串的出现不会再被替换。

②条件编译指令(如#ifdef、#ifndef、#else、#elif、#endif等等)

这些伪指令的引入使得程序员可以通过定义不同的宏来解决编译程序对哪些代码进行处理,预编译程序将根据有关的文件,将那些不必要的代码过滤掉。

③头文件包含指令(如#include <FileName> 或者 #include "FileName"等等)

在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需要加上#include 语句即可,可以不用再在此文件中重新将这些定义重复一遍。预编译程序将把头文件中的定义统统加入到它所产生的输出文件,以供编译程序对之进行处理。

头文件可以是系统提供的,也可以是开发人员自己定义的。前者(系统提供的)头文件一般保存在/usr/include目录下,#include它们要使用尖括号(<>);开发人员定义的头文件一般与C源程序位于同一目录下,#include它们要使用双引号(“”)。

④特殊符号,预编译程序可以识别一些特殊的符号

例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

总结:上面四个部分就是预编译所做的工作了,简单来说,预编译就是对源程序进行“替换”工作,生成一个没有宏定义,没有条件编译指令,没有特殊符号,将#include指向的文件插入的输出文件。这个文件的含义与未经过预处理的源文件是相同的,但因为有些东西已经被“替换”,所以它们的内容是不一样的。

2、编译、优化阶段

把高级语言翻译成机器语言的过程

经过预编译得到的输出文件中,将只有常量。如数字、字符串、变量的定义以及C语言的关键字(main、do、while、+、-等等)。预编译程序所要做的工作就是通过词法分析、语法分析和语义分析,在确定所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或者汇编代码。

优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的,在我给出的图片中,将优化阶段放在编译程序的后面,其实是比较笼统的做法。

前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件等)、复写传播,以及无用赋值的删除等等。

后一种优化,则同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC、VLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个很重要的研究话题。

经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令,方可能被机器指令执行。

总计:编译、优化阶段主要分为①词法分析②语法分析③语义分析④优化后生成相应的汇编代码,即从高级语言到汇编语言的过程。

3、汇编阶段

汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言程序,都将最终经过这一处理而得到相应的目标文件(①)。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。

目标文件由段组成。通常一个目标文件中至少有两个段(代码段和数据段):

代码段:

该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。

数据段:

主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写和可执行的。

①:UNIX下的目标文件主要有三种(可重定向文件、共享的目标文件和可执行文件):

(1)可重定向文件

其中包含有适用于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。

(2)共享的目标文件

这种文件存放了适合于在两种上下文里链接的代码和数据。第一种是链接程序可以把它与其它可重定位文件及共享的目标文件一起处理来创建另一个目标文件;第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映像。

(3)可执行文件

它包含了一个可以被操作系统创建一个进程来执行的文件。

汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理才能得到,这个就是链接程序的工作了。

总结:汇编阶段就是将汇编代码转换成机器码的过程,即汇编语言--->机器语言(二进制)

4、链接阶段

由汇编程序生成的目标文件并不能立即被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用);在程序中可能调用了某个库文件中的函数等等。所有的这些问题都需要链接程序的处理方式来解决。

根据开发者指定的同库函数的链接方式的不同,链接处理可分为两种(静态链接和动态链接):

(1)静态链接:

在这种链接方式下,函数的代码将从其所在的静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。

(2)动态链接:

在动态链接下,函数的代码被放到称作是动态链接库或共享对象的某个文件中。链接程序此时作为的只是在最终的可执行程序中记录下共享对象的名字以及其他少量的登陆信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。

对于可执行文件中的函数调用,可以分别采用动态链接和静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上的损害。

总结:链接程序就是将相关的目标文件彼此相连接,也是将在一个文件中引用的符号与另一个文件中该符号的定义相连接,使得所有文件成为一个整体。

经过上述的步骤之后,我们的C源代码便被转换成为一个可以执行的文件了。

好,下面我就操作一遍,来帮助大家加深对C源程序编译过程的理解!

(1)首先在建立一个.c文件,这里我新建了一个main.c文件,并存放于桌面的test文件夹中,内容如下:

注:如果是初学者不要在意代码内容,比较整个编译过程即可

只有一个main.c的文件哦!

这个和图片中的代码一样,是专门为新手小伙伴提供的哦!

#include <stdio.h>
#include <stdlib.h>
#define NAME "Hello"
int main()
{
    printf("%s\n%s","Hello World!",NAME);
    return 0;
}

(2)进入DOS环境下,使用gcc的命令进行测试

①预处理阶段,使用 gcc -E FileName

对比我提供的代码,是不是少了头文件而且原来的“NAME”也被替换为“Hello”了!

②编译阶段,使用gcc -S FileName

执行完这条命令,我们发现test文件夹中多出来一个main.s的文件。

③汇编过程,使用gcc -c FileName

执行完毕后,你又会发现多出一个main.o的文件。

④链接过程,使用gcc FileName

到这里,我们便有了一个.exe的可执行文件了。

这一部分确实枯燥,不过理解了这些对大家的发展是很有好处的,请大家好好理解哦!

喜欢的话关注一下呗!!!

 

  • 7
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

道人禅(armey)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值