首页 /  技术专区  /  C 屏幕太窄?试试伸展一下吧 >

C程序编译过程

1、c程序的编译过程

我们这里虽然介绍的是c程序的编译过程,但是实际上所有编译型语言的编译过程,大致是类似的。

1.1编译的四个过程

我们平时编译时,不管是通过IDE图形界面来编译的,还是通过命令行来编译的,我们感觉编译一下就完成了,然后就得到了你要的针对某os和某cpu的二进制可执行文件(机器指令的文件),但是实际上中间隐藏了四个过程,这四个过程被默默的处理了。

编译四个过程:预处理、编译、汇编、链接

四个过程中的"编译",特指其中的某个过程,这四个过程合在一起,我们也统称为编译,所以"编译"二字到底指的是第二个过程,还是全部过程的统称,这个就要看说话的"语境"了。

其实统称的"编译",完整的称法应该叫"编译链接”,只是简称为编译而已。

如果这四个过程是一次性编译完成的,这个四个过程分别会产生相应的文件,只不过中间产生的文件都是过渡性的临时文件,使用完成后就会被删除。

四个过程的总览图:

image.png

1.1.1预编译(预处理)

之所以叫预编译,表示为正式的编译做准备,预编译也被称为预处理。


                      预处理(预编译)

(1)***.c        ————————>       ***.i

如果编译过程是一次性完成的话,.i文件只是一个过渡性文件,.i被称为扩展后的c源码文件

为什么还叫c源码文件呢?

因为预处理后,只是宏定义等东西不见了,但是c源码依然还在,比如main函数,各种自己写的子函数,依然存在,所以还是被称为c源码文件。

打开.i文件后,我们是能够看的懂的,所以.i文件时ascii文件。后面会演示给大家看。

(2)预编译是以单个文件为单位来进行的

a.c ———> a.i

b.c ———> b.i

当然**.i的这个名字并不是固定的。

(3)预处理做了什么处理

1)宏替换:将宏替换为真实的宏体,比如

程序性中有使用NUM这个宏,这个宏的定义为#define NUM 100,程序中所有的NUM都会被替换为100。

2)包含头文件

将include包含的.h头文件内容,全部复制到.c文件中,因为头文件中定义了类型、宏、函数声明等等,这些都是函数调用会用到的,你调用某个函数时,就必须包含这个函数要的头文件。

疑问:头文件那么多内容,都包含进去的话,不会太多了吗?

编译时,编译器只使用要用的东西,用完后包含的内容都会被丢弃,实际上并不占空间。

3)条件编译

处理#if#endif 这类的东西

4)处理一些特殊的预处理关键宁

有关预处理的宏定义、头文件包含、条件编译、特殊预处理关键字,会在后面专门的《预处理》章节中讲到。


1.1.2编译

                                编译

( 1)***.i        ————————>       *** .s

s:汇编文件

(2)同样也是以单个文件为单位来进行的

(3)编译做了什么

将c语法的c源码,翻译为汇编语法的汇编源码。

(4) .s是ascii码文件

因为汇编也是人能看懂的文字编码形式,所以.s汇编文件也是ASCII码文件。


1.1.3 汇编

                                汇编

( 1)***s        ————————>       ***.o

.o文件是纯二进制文件


因为.o中放的是纯二进制的机器指令,所以我们打开后看不懂。

(2)同样也是以文件为单位来进行的

(3)汇编做什么

将AscII的汇编源码,翻译为能够被cPu执行的机器指令,,o文件中放的就是机器指令。但是.o文件还无法运行,需要链接后才能运行。


1.1.4链接

***1.o      ————————\

***2.o      —————————\

                                                 /    a.out (可执行文件)

. . . .        —————————/

***n.o    ————————/


(1)链接(连接)做了什么

1)将众多的.o合成一个完整的可执行文件

.o实现相互依赖的,比如a.o中调用的函数,被定义在了b.o中,如果不链接在一起的话,是无法工作的。

2)链接时,需要加入额外的启动代码

这个启动代码并不是我们自己写的,main函数是由启动代码调用的,我们的程序是从启动代码开始运行的,后面会介绍启动代码是怎么来的。

3)链接为一个可执行文件时,需要进行符号解析和地址重定位

后面会介绍什么事符号解析和地址重定位。

(2) Linux下可执行文件命名问题

在windows下,可执行的尾缀时.exe,但是在Linux下,可执行文件没有固定的尾缀。

(3)如果整个c工程就一个.c,最后得到的只有一个.o,此时还需要链接吗,可不可以直接执行呢?

同样的要链接后才能运行,因为链接后才有启动代码和重定位后的地址,否者无法运行。


1.2了解编译器集合

我们在前面就说过,编译器并不是一个单独的程序,而是一堆程序的集合。

为了更好的了解编译的四个过程,我们需要大概的弄清楚"编译器集合"组成。

对于"编译器集合"的组成,我们作为应用软件开发者了解到本章介绍的程度就可以了,至于更加深入的内容,那就是"编译器开发者"和"逆向破解者"所应该掌握的内容。

所以对于更多更深内容,如果是站在应用开发角度的话,我们不建议大家再去更加深入,作为应用开发来说,再深入的话意义不大。


1.2.1 codeblocks的编译器集合

(1) codeblocks安装目录

codeblocks的编译器集合就放在它的安装目录下。

(2)编译的四个过程,必须用到的基本程序

cpp、cc1、 as、collect2/ ld、 gcc

gcc编译时四个过程自动完成,我们既然已经知道了这四个过程,那么我们就自己一步一步实现这四个过程,然后得到最终的可执行文件。

我们讲这四个过程的主要目的是什么呢?

熟悉这四个编译过程,了解每个过程做了什么事,了解每个过程调用了编译器集合中什么程序

疑问:你怎么知道编译四过程使用的就是cpp、cc1、as、collect2、gcc这些程序呢?我们后面再来回答这个问题。


1) cpp(Mingw\bin\ )

(a)预编译程序(预编译器、预处理器):实现预编译

为了方便我们查看预编译后的结果,我们先在.c中加入宏、条件编译、头文件包含。

(b)演示

cpp helloworld.c -o helloworld.i

o选项用于指定目标文件,表示将预处理后的结果保存到.i文件中。

-验证.i文件是不是ascii文件。

–验证预处理后.c中的宏、 include、条件编译,在.i中还能否见到。


我们用一个helloworld.c文件做演示。

image.png

helloworld.c文件通过cpp命令预编译为helloworld.i文件。

image.png

我们打开.i文件观看,是一个我们能看懂的文件,里面包含了头部信息排除了判断为假的内容,宏定义也赋值到了变量上面。

image.png


2 ) cc1 (Mingw\libexec\gcc \mingw32\4.9.2\)

(a)编译程序(编译器):将c源码翻译为汇编源码(b)演示

..\libexec\gcc\mingw32\4.7.1\cc1 helloworld.i -o helloworld.s

验证.s文件是不是ASCII文件。

(c) cc1值得注意的地方

其实cc1本身也包含cpp预处理的功能,也就是说可以直接使用cc1将.c—>.s,cc1会完成之前的预处理的功能。

..\libezec\gcc\mingw32\4.7.1\cc1 helloworld.c -o helloworld.s

不过以上命令并不能被成功执行,因为还缺参数,他会提示找不到头文件,至于缺什么参数,我们这里就不关心了。

image.png

.s文件被编译为汇编文件。

image.png


3 ) as (Mingw\bin\ )

(a)汇编程序(汇编器):将汇编源码翻译为纯二进制的机器指令,放到.o文件中

(b)演示

as helloworld.s -o he1loworld.o


image.png

我们看一下汇编完成后的.o文件,已经变成我们看不懂的二进制文件了。

image.png


4) ld、 collect2

ld路径:Mingw\ binl. MinGw\mingw32\bin

collect2路径: Mingw\libexec\gcc\mingw32\4.7.1\collect2

(a)链接程序(链接器(静态链接器))

将所有的.o文件(自己的、编译器提供的)和库(动态库、静态库)链接在一起,得到可以运行的可执行文件。

(b) collect2 和ld之间的关系

collect2是对ld进一步封装得到的,这两个都可以用于链接。

(c)演示

实际上我们完全可以自己调用collect2和ld这两个程序(命令)来进行链接,但是链接并不是一件容易的事情,链接的时候需要跟大量的参数和选项,这些参数和选项我们自己并不清楚,所以我们自己调用eollect2和ld来链接的话,实际上操作起来比较困难。

所以链接的话,我们直接使用gcc程序来链接,gcc会自动调用collect2或者ld来链接,并且自动指定需要的各种的选项和参数,我们并不是需要关心。

gcc helloworld.o -o helloworld

或者

gcc helloworld.o

(如果不指定可执行文件名字的话,默认为a.exe)

image.png

我们打开helloworld.exe查看。
image.png


5 ) gcc/mingw32-gcc/g++/c++

其实gcc/mingwgcc/g++这几个都能编译c程序。

(a) gcc/mingw32-gcc/g++/C++关系

其中mingw32-gcc是对gcc继续做封装后得到的。

C++/g++是用来编译c++程序的,但是由于c++程序兼容c,所以c++/g+也能编译c程序。

正式因为编译集合中包含了g++,所以我们也能使用codeblocks来写c++程序的,而且codeblocks这个IDE本身好像就是c++写的。

(b) gcc/mingw32-gcc/g++/C++程序的作用

gcc/mingw32-gcc/g++/C++其实是总的调度程序,它按照需求去调用cpp/cc1/as/collect2等程序,完成对应四个过程。


通过前面的讲解知道,虽然我们能够自己调用cpp/cc1/as/collect2/ld来完成四个过程,得到最后的可执行文件,但是存在如下缺点。

-每个阶段的程序名都不一样,不方便记忆

第一阶段叫cpp,第二阶段叫cc1等,老实讲,时间久了我也忘了。

有了gcc这个总调度程序后,不管是哪个阶段,对于我们来说,只需要记住gcc这一个程序即可。你想实现那个阶段,通过gcc即可实现,通过给gcc指定不同的选项,gcc可以自动调用cpp/cc1/as/ collect2/1d中所需要的程序来实现对应的过程。

-如果每个阶段都我们自己亲自执行cpp/cc1/as/collect2/ld这些程序来编译的话,速度太慢了

有了gcc后,虽然可以通过指定选项,分别实现每个过程,但是实际上也可以调用gcc一次性快速完成四个过程,gcc会自动调用cpp/cc1/as/collect2/ld来完成。

次性完成时,中间产生的.i/.s/.o都是临时文件,编译后会被自动删除,这些文件我们并不关心,我们关心的只是最后的可执行文件。

使用gcc这个总调度程序,一次性完成所有过程时,编译速度非常快,用起来非常方便。

(c) gcc/mingw32-gcc/g++/c++的各种选项

它们几个的使用方式都是一样的,所以我们就以gcc为例来讲。

gcc的选项很多,先介绍常用的E/S/c/g选项,然后在介绍其它一些个不常用选项。至于其它的非常不常用的选项,我们这里不介绍,用到时大家自己研究搞定。

-E

只得到.i的扩展c源文件

演示

gcc -E helloworld.c -o helloworld.i

gcc会自动调用cpp或者cc1来进行预处理。如果不写目标文件,就直接输出到控制台。

疑问: gcc -o helloworld.i -E helloworld.c,这么写可以吗?

可以


-S

只编译到.s文件+演示

gcc -S helloworld.i -o helloworld.s

gcc会自动调用cc1,将.i编译为.s。

如果不写目标文件,会自动保存为同名的.s文件

疑问: gcc -s helloworld.c -o helloworld.s 可以吗?

可以,gcc自动调用cc1时, cc1先预编译,然后再编译。


-c

只编译得到.o文件

演示

1 ) 

gcc -c helloworld.s -o helloworld.o

自动调用as进行汇编,将.s中的汇编源码翻译为机器指令,并保存到.o

2 ) 

gcc -c helloworld.i -o helloworld.o

(a)调用cc1编译得到临时.s

(b)调用as将.s汇编得到.o

3 ) 

gcc -c helloworld.c -o helloworld.o

(a)调用cc1预编译、编译得到临时的.s‘’

(b)调用as将.s汇编得到.o

如果不写目标文件,会自动的保存为同名的.o文件


-直接得到可执行文件

+演示

gcc helloworld.c **.c -o helloworld.exe

gcc helloworl4.i **.i -o helloworld.exe

gcc helloworld.s **.s -o helloworld.exe

gcc helloworld.o **.o -o helloworld.exe


-g

如果要进行debug调试的话,通过指定-g选项,会加入调试信息,没有调试信息是无法进行调试的。debug调试:后面的课程再介绍。

其它不常用选项

o0/o1/o2/0s/o3:指定优化级别,00< o1 < 02< Os < 03

gcc hellowolrd.c -o helloworld.exe -o1

如果不指定有优化级别的话,默认就是o1级别,有关优化的更多情况,后面章节再介绍。


-Wall:

gcc hellowolrd.c -o helloworld -Wall

表示编译时将所有的警告信息都显示出来,如果不指定的话,默认只显示重要的警告,不重要的警告就不显示。

比如,有一个变量定义了但是没有使用,就是一个不重要的警告。如果指定了-Wall选项,会警告你没有使用,否者不提示这个警告。

警告真的不重要吗?

初学c的时候,来时会告诉你警告没关系,但是在实际开发中警告是不能有的,为什么?

对于程序的警告来说,虽然不是"编译链接"严重错误,但是在程序的运行过程中,这些警告可能会演变为威胁程序正常运行的错误,所以警告是程序的隐患,因此在实际开发中,编译时必须将警告排除。


-s

对可执行文件进行瘦身

gcc hellowolrd.c -o helloworld -s

不指定-s时,可执行文件都会包含调试等信息,用以实现程序的调试,但是当程序被调试没有bug后发布时,发布的程序就不再需要这些信息了,指定-s选项后,gcc编译时会对可执行文件进行瘦身,以去掉这些信息。


-std

指定编译时准守的语言标准,c的标准目前有c89、c90、c99、c11

gcc helloworld.c -o helloworld -std=c11//编译时,按照c11标准来解析c语法格式

语言在发展的过程中,每过一段时间就会修改、增加语法特性,然后重新指定语法标准,编译器在编译时就是按照标准来翻译语言的语法格式,c语言也是这样的。

如果gcc时指定某个c标准的话,就会使用该标准来编译,如果不指定的话,就使用gcc默认设置的标准来编译。

-股来说,新的标准都是兼容旧标准的,但是反过来就不行,如果你的程序使用了最新标准的语法特性,而在编译时指定的确是旧标准的话,就会编译出错,因为旧标准没有这些信的特性。

不过一般来说我们不用关心标准问题,因为我们使用的都是最常见c语法特性,不管哪个标准都是支持的,所以不用指定特定的标准,gcc设置的默认标准就支持。


-v

显示编译过程的详细信息

gcc helloworld.c -o helloworld -v//显示预处理、编译、汇编、链接,所有过程的详细信息。

回答之前的问题:我怎么知道编译时使用的是as、cc1、collect2、ld这些程序的?

通过加-v选项,阅读编译过程的详细信息知道的,后面会分析这些详细信息。

疑问:单个过程可以加-v吗?

可以,显示的就是单个过程的详细信息,比如

gcc -E helloworld.c -o helloworld.i -v//只显示预处理的详细信息。




0/200
图片验证码