|
GCC(GNU Compiler Collection)是Linux下最常用的C语言编译器,是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。同时它可以通过不同的前端模块来支持各种语言,如Java、Fortran、Pascal、Modula-3和Ada等。 : P& B- h H6 k( C- I 穿插一个玩笑: GNU意思是GNU’s not Unix而非角马。然而GNU还是一个未拆分的连词,这其实是一个源于hacker的幽默:GNU是一个回文游戏,第一个字母G是凑数的,你当然可以叫他做ANU或者BNU。下面开始。 + {0 m+ M' }# l t4 Y 一.CC编译程序过程分四个阶段 ◆ 预处理(Pre-Processing) : b; y* g/ w) t8 [+ }; S ◆ 编译(Compiling) ◆ 汇编(Assembling) ◆ 链接(Linking) * F. P C' n# s* A6 b Linux程序员可以根据自己的需要让GCC在编译的任何阶段结束转去检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。如同其他的编译器,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。 GCC提供了30多条警告信息和三个警告级别,使用它们有助于增强程序的稳定性和可移植性。此外,GCC还对标准的C和C++语言进行了大量的扩展,提高程序的执行效率,有助于编译器进行代码优化,能够减轻编程的工作量。 . P( b2 o3 t5 s/ }/ `( U 二.简单编译命令 我们以Hello world程序来开始我们的学习。代码如下: /* hello.c */ #include 1 S+ [* o9 q, f& ] int main(void) 4 C6 `) L, m: t { + H. L( q8 ~+ H printf ("Hello world!\n"); " S/ d- N ~+ G( f return 0; 8 E- M$ I- d" C$ t l: W, E: e- r } 1. 执行如下命令:$ gcc -o hello hello.c 运行如下 : $ ./hello 输出: Hello,world! ) a( Z* Y: G: H% y3 X# q4 ~ 2. 我们也可以分步编译如下: (1) $ gcc –E hello.c -o hello.i , ~: I6 L/ h ?8 x* A- @! u //预处理结束 ( C8 a% J8 L: O: c4 j$ B6 U //这时候你看一下hello.i ,可以看到插进去了很多东西。 : v Y3 C5 c+ Q4 x- M0 T5 _ (2) $ gcc –S hello.i + j8 v7 {, f" t/ K5 y //生成汇编代码后结束 (3) $ gcc –c hello.c " C* p! g0 v# o' f* \( F 或者: ) r' ]7 G6 Q9 s" h+ z5 s $ gcc -c hello.c –o hello.o g% R; Y0 n4 R" T4 H) s! j 或者: ( w4 n1 ?; W1 N) U1 _ $ gcc -c hello.i -o hello.o //编译结束 //生成 hello.o文件 (4) $ gcc hello.o –o hello.o - n) V, M5 t, V+ w0 N0 d 或者: - ^- v7 C8 T* b $ gcc –o hello hello.c : M6 a+ y; q% @ //链接完毕,生成可执行代码 3. 我们可以把几个文件一同编译生成同一个可执行文件。 : e4 m7 d' i4 g- |4 O$ t$ ` 比如:一个工程有main.c foo.c def.c生成foo的可执行文件。 ) S1 g" o0 K0 {$ |; w1 R 编译命令如下: 0 W: D5 }$ `9 I( V' x4 o: O $ gcc –c main.c foo.c def.c –o foo + B9 m7 {0 r! |4 v* t3 K7 I+ T 或者: - D. p+ N/ T& T# r3 s $ gcc –o foo main.c foo.c def.c 三.库依赖 函数库是一些头文件(.h)和库文件(.so或者.a)的集合。Linux下的大多数函数都默认将头文件放到/usr/include/目录下,而库文件则放到/usr/lib/目录下,但并非绝对如此。因此GCC设有添加头文件和库文件的编译选项开关。 1. 添加头文件:-I - m# k1 Y# m7 P- p3 a 例如在/home/work/include/目录下有编译foo.c所需头文件def.h,为了让GCC能找到它们,就需要使用-I选项: # y: \, G( \9 I $ gcc foo.c -I /home/work/include/def.h -o foo 2. 添加库文件:-L 例如在/home/work/lib/目录下有链接所需库文件libdef.so,为了让GCC能找到它们,就需要使用-L选项: . z+ x1 i. L4 S) t! V* J $ gcc foo.c –L /home/work/lib –ldef.a –o foo / ~4 l% v3 o4 C& H$ ^& i. M% z 说明:-l选项指示GCC去连接库文件libdef.so。Linux下的库文件命名有一个约定,即库文件以lib三个字母开头,因为所有的库文件都遵循这个约定,故在用-l选项指定链接的库文件名时可以省去lib三个字母。 " S! A, g& N+ U, ?' g7 W( l( J 题外语: Linux下的库文件分为动态链接库(.so文件)和静态链接库(.a文件)。GCC默认为动态库优先,若想在动态库和静态库同时存在的时候链接静态库需要指明为-static选项。比如上例中如还有一个libdef.a而你想链接libdef.a时候命令如下: / a6 P- E) N, ~* I $ gcc foo.c –L /home/work/lib –static –ldef.a –o foo + O; F- {7 m: O* ^ 四.代码优化 GCC提供不同程度的代码优化功能。开关选项是:-On,n取值为0到3。默认为1。-O0表示没有优化,而-O3是最高优化。优化级别越高代码运行越快,但并不是所有代码都能够加载最高优化,而应该视具体情况而定。但一般都使用-O2选项,因为它在优化长度、编译时间和代码大小之间,取得了一个比较理想的平衡点。 ! [8 F# |4 A- I6 o0 }# d6 h 以下这段说的比较详细: 9 v) M( \+ K* g. R- d 编译时使用选项-O可以告诉GCC同时减小代码的长度和执行时间,其效果等价于-O1。在这一级别上能够进行的优化类型虽然取决于目标处理器,但一般都会包括线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。选项-O2告诉GCC除了完成所有-O1级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。选项-O3则除了完成所有-O2级别的优化之外,还包括循环展开和其它一些与处理器特性相关的优化工作。通常来说,数字越大优化的等级越高,同时也就意味着程序的运行速度越快。 " `0 @) {: Y, u8 B 下面通过具体实例来感受一下GCC的代码优化功能,所用程序如清单3所示。 4 f" G; i6 j3 a0 {9 T' }% V$ w /* optimize.c */ #include <stdio.h> int main(void)/ Z6 O! M, A5 ?7 L* M9 Q3 U { double counter; double result; $ Y7 Y' v- d3 q/ ? W. _double temp; for (counter = 0; counter < 2000.0 * 2000.0 * 2000.0 / 20.0 + 2020; counter += (5 - 1) / 4) { temp = counter / 1979; result = counter; }. }& u! m3 l; k* B& b printf("Result is %lf\n", result);# b) i4 w7 b- @ return 0;8 i, v% C3 }& N1 J: A9 U }. ^0 Z; d8 m ] , l( A A5 D% a) } 首先不加任何优化选项进行编译:6 I) M6 C1 d, u # gcc -Wall optimize.c -o optimize 6 _& [! e/ T7 j: ^8 }3 _6 f# n) Y7 R4 C0 f8 V+ A3 q1 B7 g8 r2 Q' o 借助Linux提供的time命令,可以大致统计出该程序在运行时所需要的时间: # time ./optimize Result is 400002019.000000 real 0m14.942s' L4 o8 H k9 k user 0m14.940s sys 0m0.000s 接下去使用优化选项来对代码进行优化处理:* L) l/ f9 P8 z) N$ \- T # gcc -Wall -O optimize.c -o optimize # ?6 j, M U: K! \3 j) {7 R: P2 E% a T5 X4 M8 [ / H; l# n `+ j- P+ B5 \1 `在同样的条件下再次测试一下运行时间:, Q& i7 s6 U" c: c9 ?+ C # time ./optimize" A; [: M9 x; Q3 d Result is 400002019.000000 real 0m3.256s user 0m3.240s! {3 i6 T- l0 K3 B sys 0m0.000s ! P6 {0 t7 s5 T. d: l" D 对比两次执行的输出结果不难看出,程序的性能的确得到了很大幅度的改善,由原来的14秒缩短到了3秒。这个例子是专门针对GCC的优化功能而设计的,因此优化前后程序的执行速度发生了很大的改变。尽管GCC的代码优化功能非常强大,但作为一名优秀的Linux程序员,首先还是要力求能够手工编写出高质量的代码。如果编写的代码简短,并且逻辑性强,编译器就不会做更多的工作,甚至根本用不着优化。 3 n$ e1 f( E i, u; L0 G, a4 A 优化虽然能够给程序带来更好的执行性能,但在如下一些场合中应该避免优化代码:! {4 |; F8 B) @* Y ◆ 程序开发的时候优化等级越高,消耗在编译上的时间就越长,因此在开发的时候最好不要使用优化选项,只有到软件发行或开发结束的时候,才考虑对最终生成的代码进行优化。 & x$ J8 z5 B! y9 C4 D6 z5 ^4 `! q ◆ 资源受限的时候一些优化选项会增加可执行代码的体积,如果程序在运行时能够申请到的内存资源非常紧张(如一些实时嵌入式设备),那就不要对代码进行优化,因为由这带来的负面影响可能会产生非常严重的后果。 ◆ 跟踪调试的时候在对代码进行优化的时候,某些代码可能会被删除或改写,或者为了取得更佳的性能而进行重组,从而使跟踪和调试变得异常困难。 |
微信公众号
手机版