全部博文(95)
分类: LINUX
2008-04-12 22:50:37
以前对编译系统没有多大了解,仅仅当成一个工具来使用,从没有对其进行从内而外的理解。前几天看了关于GCC的东西,现在想来,好好总结一下,怕过了两天又忘了,算是留下研究的证明吧, 呵呵。
1. 为什么使用GCC。关于这个问题,从来没有问过自己,是啊,有很多的编译器,为什么使用GCC呢,看一下GCC的全称就知道答案了:GNU Compiler Collection. 这就意味着其遵循GNU,人人都可以用,人人都可以优化,也意味它是安全的,全世界几亿双眼睛盯着呢,敢造次吗!它会越来越进化,因为全世界几亿人都时刻准备着为了它的优化做奋斗呢,就跟实现共产主义似的。
2. 编译器的处理过程:
编译过程一般可以分为四个部分,分别由四个功能模块来完成。
从功能来看:
源程序-> 预处理cpp -> 编译ccl -> 汇编as -> 链接ld ->可执行文件
从文件流来看:
|
|
|
|
预处理cpp
编译ccl 汇编as 链接ld
。c―――――>。i―――――>。S―――――>。o―――――>可执行文件
现罗列一下编译过程中的文件的类型:
原始程序码的扩展名指出所用编写程序所用的语言,以及相对应的处理方法:
.c C 原始程序 ; 预处理、编译、汇编
.C C++ 原始程序 ; 预处理、编译、汇编
.cc C++ 原始程序 ; 预处理、编译、汇编
.cxx C++ 原始程序 ; 预处理、编译、汇编
.m Objective-C 原始程序 ; 预处理、编译、汇编
.i 已经过预处理之 C 原始程序 ; 编译、汇编
.ii 已经过预处理之 C++ 原始程序 ; 编译、汇编
.s 组合语言原始程序 ; 汇编
.S 组合语言原始程序 ; 预处理、汇编
.h 预处理文件(标头文件) ; (不常出现在指令行)
其他扩展名的文件是由连结程序来处理,通常有:
.o Object file
.a Archive file
.so Shared object file
Linux程序员可以根据自己的需要让GCC在编译的任何阶段结束,以便检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制(可对任一中间类型的代码进行处理即可从任一口入,也可以生成任一中间段的代码即可从任一口出),以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。和其它常用的编译器一样,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更 高的代码。GCC提供了30多条警告信息和三个警告级别,使用它们有助于增强程序的稳定性和可移植性。此外,GCC还对标准的C和C++语言进行了大量的扩展,提高程序的执行效率,有助于编译器进行代码优化,能够减轻编程的工作量。从程序员的角度看,只需简单地执行一条GCC命令就可以了,但从编译器的角度来看,却需要完成一系列非常繁杂的工作。首先,GCC需要调用预处理程序 cpp,由它负责展开在源文件中定义的宏,并向其中插入“#include”语句所包含的内容;接着,GCC会调用ccl和as将处理后的源代码编译成目标代码;最后,GCC会调用链接程序ld,把生成的目标代码链接成一个可执行程序。
3. GCC编译器的功能
(1) 工作流程的介绍
预编译:源代码中的预编译指示以"#"为前缀。对#include“”与#include<>作一下解释。
#include“”:要求预编译模块先到“…”指定的位置去找->再到-Idirname中去找->到系统默认的目录(一般为/usr/include)中找。
#include<>:到系统默认的目录(一般为/usr/include)中找。
源代码经过预编译模块CPP完成如下:
这就使我们能够在整个源文件中使用符号常量,而符号常量是在一个地方定义的,如果它的值发生了变化,所有使用符号常量的地方 都能自动更新。如果想就从源程序->生成预处理的代码,命令如下:
gcc –E sample.c –o sample.i
如果要指定对include中包含文件的收搜位置则如下(如果放在系统默认的位置(/usr/include)则不用特别指出):
gcc –E sample.c -I dirname –o sample.i
但是在实践中,几乎不需要单独使用 -E 选项,而是让它把输出传送给编译器。
编译:其实就是作语法检查,作为一个中间步骤,gcc把代码翻译成汇编语言(。S)。它一定要这样做,它必须通过分析你的代码而搞清楚你究竟想要做什么。如果你犯了语法错误,它就会告诉你,这样编译就失败了。
单独的过程命令如下:
gcc –S sample.c / sample.i -o sample.S
汇编:as 把汇编语言代码转换为目标代码。事实上目标代码并不能在CPU上运行, 但它离完成已经很近了。编译器选项 -c 把 .c 文件转换为以 .o 为扩展名的目标文件。
单独的过程命令如下:
gcc –c sample.c / sample.i / sample.S -o sample.o
如果不用-o选项,系统自动生成sample.o文件。汇编过程生成目标代码。
链接:为什么要链接。a. 我们知道一个程序在执行时都是从main函数开始执行的,一个程序可以由许多源程序构成且只有一个源程序中含有main函数,经过前边这三边,生成相应的目标文件,这些普通的目标程序只有跟含有main函数的目标程序经过链接才能生成一个可执行文件。b. 由于现在库的使用(头文件(变量和函数的声明即接口,所以在安装库时要注意库的安装位置,以便于应用程序对头文件的引用)+函数实现(函数的定义的目标代码)),在预编译阶段已经处理了头文件相关的操作,头文件中有函数的声明,但是没有相关函数的实现目标代码,如果不连接,在执行时就不能找到函数的具体实现信息,无法实现函数的功能。所在必须在执行前,实行函数的声明与实现的一一对应。头文件的处理是在第一阶段预处理中完成的,而库的链接是在第四个阶段中完成的。
单独的命令形式:
gcc sample.c / sample.i / sample.S / sample.o -o sample
如果要指定链接过程中的库的位置则如下(如果库是安装在系统默认的位置(/usr/lib)则不用特别指出,系统在链接时会自动去那个地方找库文件的目标代码部分。另外库文件在生成时按系统默认规定都以lib开头,所以在写–lname时,实是省略了前边的lib,相当于–llibname):
gcc sample.c / sample.i / sample.S / sample.o -L dirname –lname -o sample
(2) 警告提示与代码优化功能:
首先区别报错与警告的区别,系统在报错时是停止编译的,而在警告时是不停止编译的。虽然警告能继续编译过程,但是正确对待警告信息,能帮助我们提高代码的质量。一般以W开头的选项都与警告有关。即在命令上加上-Wall选项,就打开了所有的警告功能。另一个常用编译器选项是优化选项 -O# (即 -O2估,是字母O,optimize)。这是告诉编译器你需要什么级别的优化。编译器具有一整套技巧可以使你的代码运行得更快一点。 对于一般的小程序,你可能注意不到差别,但对于大型程序来说,它可以大幅度提高运行速度。你会经常碰到它,所以你应该知道它的意思。一般用-o2。
(3) 调试功能:这一部分没有进行怎么实现,偶就不敢乱造次了,留给以后吧。但是介绍2个调试器。
GDB:GNU Debugger.
KDbg:KDE’s Debugger
4. 常用的命令选项
GCC作为Linux下C/C++重要的编译环境,功能强大,编译选项繁多。为了方便大家日后编译方便,在此将常用的选项及说明列数如下:
-c : 通知GCC取消链接步骤,即编译源码并在最后生成目标文件;
-Dmacro: 定义指定的宏,使它能够通过源码中的#ifdef进行检验;
-E : 不经过编译预处理程序的输出而输送至标准输出;
-g3: 获得有关调试程序的详细信息,它不能与-o选项联合使用;
-I directory: 将directory所指出的目录加入到程序头文件目录列表中,是预编译过程中使用的参数; 在程序设计中,如果我们需要的这种包含文件分别分布在不同的目录中,就需要逐个使用-I选项给出搜索路径。
-L directory: 将directory所指出的目录加入到程序函数档案库文件的目录列表中,是在连接过程中使用的参数。在预设状态下,连接程序ld在系统的预设路径中(如/usr/lib)寻找所需要的库文件, 但是这个选项告诉连接程序,首先到-L指定的目录中去寻找,然后到系统预设路径中寻找,如果函数库存放在多个目录下,就需要依次使用这个选项,给出相应的存放目录。
-llibrary: 提示链接程序在创建最终可执行文件时包含指定的库;
-shared: 将文件编译成动态库。
-O、-O2、-O3: 将优化状态打开,该选项不能与-g选项联合使用;
-S: 要求编译程序生成来自源代码的汇编程序输出;
-Wall: 启动所有警报;
-Werror: 在发生警报时取消编译操作,即把报警当作是错误;
-w : 禁止所有的报警。
(-I dir和-L dir,就是通知系统,其所指的目录优于系统默认的目录)