GCC(GNU Compiler Collection)是Linux下最常用的C语言编译器,是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。同时它可以通过不同的前端模块来支持各种语言,如Java、Fortran、Pascal、Modula-3和Ada等。 穿插一个玩笑: GNU意思是GNU’s not Unix而非角马。然而GNU还是一个未拆分的连词,这其实是一个源于hacker的幽默:GNU是一个回文游戏,第一个字母G是凑数的,你当然可以叫他做ANU或者BNU。下面开始。 一.CC编译程序过程分四个阶段 ◆ 预处理(Pre-Processing) ◆ 编译(Compiling) ◆ 汇编(Assembling)- ◆ 链接(Linking) Linux程序员可以根据自己的需要让GCC在编译的任何阶段结束转去检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。如同其他的编译器,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。 GCC提供了30多条警告信息和三个警告级别,使用它们有助于增强程序的稳定性和可移植性。此外,GCC还对标准的C和C++语言进行了大量的扩展,提高程序的执行效率,有助于编译器进行代码优化,能够减轻编程的工作量。 二.简单编译命令 我们以Hello world程序来开始我们的学习。代码如下: /* hello.c */2 #include int main(void) {, printf ("Hello world!\n");* return 0; } 1. 执行如下命令:$ gcc -o hello hello.c 运行如下 : $ ./hello 输出: Hello,world! 2. 我们也可以分步编译如下: (1) $ gcc –E hello.c -o hello.i5 K3 n4 {7 d) Y, [% D Y " G# e$ ^! R3 _' J) ]% K0 o //预处理结束 //这时候你看一下hello.i ,可以看到插进去了很多东西。: f! Y# U7 }3 t" T8 ~3 w0 M (2) $ gcc –S hello.i - {9 e( n( R [5 d$ w / h1 ?; q* T8 n3 i //生成汇编代码后结束 ( S2 C' z3 g- n6 c6 Z 7 {4 A4 q$ `2 q& s4 Z (3) $ gcc –c hello.c ) d7 N5 B1 z# r5 _5 q 或者: $ gcc -c hello.c –o hello.o2 F; T/ D+ u; Y0 A- L6 T# s0 n, C 或者:: o' [2 S& m; _$ U 9 d# _1 e8 l. L6 _* M' C; F K/ g $ gcc -c hello.i -o hello.o w* F; W0 L& ]' J ) |2 C9 @& U- V5 H2 S# t : M: c# ^' Q8 x, x' K+ p //编译结束5 {; s9 O. ]$ i; Q4 o& L + \" e" Q6 N; ^8 U4 V. y% I$ q //生成 hello.o文件 # f/ j5 q/ ~% u# _7 b N" L" Y! @- `5 D (4) $ gcc hello.o –o hello.o0 h/ G/ M) D( A1 {; ?: ~& v$ ? {$ Q7 E }8 g3 B 或者:9 x. R; V o! d4 {7 _ 5 Y: n& A P$ c3 d; x& D $ m! L7 k* ~9 R9 ^% ]% h3 W( v4 W, S: ` $ gcc –o hello hello.c3 V1 P$ E8 x1 f % U7 |* o g- B, d 4 {) x3 T; A# _* @ //链接完毕,生成可执行代码, }& p/ s; O8 S, C1 g- z% T7 U* e: V 1 z" Y! k( q3 S7 l6 @, D' Z 3. 我们可以把几个文件一同编译生成同一个可执行文件。 ( N& t% B* v" z1 H5 t% ]- s# T/ o 比如:一个工程有main.c foo.c def.c生成foo的可执行文件。 / M. H4 u5 W8 l) r 编译命令如下: ! R7 S4 ]$ u$ q $ gcc –c main.c foo.c def.c –o foo% C x9 h' c% Z3 ]/ _/ }( v , h! R" ]/ n6 l0 [/ |0 p* Y+ { 3 z. F8 i8 _8 e2 S 或者:2 d* b8 `" @; G9 g' a 4 g* g: I0 F0 s" @6 [( k" b& {& N $ gcc –o foo main.c foo.c def.c3 Z- e, o `: N+ F! [' | 6 R* ?" N( K# F# ] _- }& R 三.库依赖% U6 I# v% q- b" F ( g; U, n" v: Y$ @: u6 W 函数库是一些头文件(.h)和库文件(.so或者.a)的集合。Linux下的大多数函数都默认将头文件放到/usr/include/目录下,而库文件则放到/usr/lib/目录下,但并非绝对如此。因此GCC设有添加头文件和库文件的编译选项开关。* x* v5 {$ R0 V3 z : s8 b$ r. i7 E+ P 1. 添加头文件:-I, y$ [1 }( z/ R E; @! T 5 I4 X- w5 O+ K9 Y1 _8 e 例如在/home/work/include/目录下有编译foo.c所需头文件def.h,为了让GCC能找到它们,就需要使用-I选项: 6 U3 w1 L7 [6 q' Y. H 1 w/ m8 D5 K! g8 D( J. v $ gcc foo.c -I /home/work/include/def.h -o foo * }7 K) [8 n3 |- k # @- f# V0 v+ V- }5 j! g& V 2. 添加库文件:-L$ x I o0 g5 v) T5 f 例如在/home/work/lib/目录下有链接所需库文件libdef.so,为了让GCC能找到它们,就需要使用-L选项:% b* p3 Z" v0 k8 t4 b8 [. ^$ ^ ) o5 }0 ]+ Y( p% `: z $ gcc foo.c –L /home/work/lib –ldef.a –o foo 说明:-l选项指示GCC去连接库文件libdef.so。Linux下的库文件命名有一个约定,即库文件以lib三个字母开头,因为所有的库文件都遵循这个约定,故在用-l选项指定链接的库文件名时可以省去lib三个字母。, {! A0 \% b3 R! u# A5 I ( ~2 w9 E D' j9 J7 n7 r5 @# p: u, K 题外语:' b; u( ~# x2 t ' J* b4 R8 m) p! S Linux下的库文件分为动态链接库(.so文件)和静态链接库(.a文件)。GCC默认为动态库优先,若想在动态库和静态库同时存在的时候链接静态库需要指明为-static选项。比如上例中如还有一个libdef.a而你想链接libdef.a时候命令如下:# {: p+ J2 v9 u* Y9 ]5 a/ j ! y& T5 J. y0 Z0 L3 B $ gcc foo.c –L /home/work/lib –static –ldef.a –o foo ) f% I# E( |7 h 四.代码优化6 P: q3 |4 }# U- K7 b8 V 4 b: G3 y/ P, a" l# B7 C2 y " ]3 V; H( }! d4 c* u8 y GCC提供不同程度的代码优化功能。开关选项是:-On,n取值为0到3。默认为1。-O0表示没有优化,而-O3是最高优化。优化级别越高代码运行越快,但并不是所有代码都能够加载最高优化,而应该视具体情况而定。但一般都使用-O2选项,因为它在优化长度、编译时间和代码大小之间,取得了一个比较理想的平衡点。* X7 O/ n& Q; z6 t1 U2 { - T( ?5 G) @! M7 D+ p " J, y; T. S- j7 i! `7 q 以下这段说的比较详细:* q3 p% P% M& j, \8 ?/ l # H7 T" _! x, d' e 编译时使用选项-O可以告诉GCC同时减小代码的长度和执行时间,其效果等价于-O1。在这一级别上能够进行的优化类型虽然取决于目标处理器,但一般都会包括线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。选项-O2告诉GCC除了完成所有-O1级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。选项-O3则除了完成所有-O2级别的优化之外,还包括循环展开和其它一些与处理器特性相关的优化工作。通常来说,数字越大优化的等级越高,同时也就意味着程序的运行速度越快。 1 B! z, k- A, ? 下面通过具体实例来感受一下GCC的代码优化功能,所用程序如清单3所示。' t7 Z) M1 \' l! U 8 f r) W* M8 Y # T+ h% \- G. I0 g /* optimize.c *// C5 R) y4 a) [- T) i, T #include <stdio.h>$ x% z( i2 B% }$ ^ int main(void) { double counter;# i v+ T5 e, n5 _. L2 ?* }9 j double result;) E" H5 ` u [- P double temp; for (counter = 0; counter < 2000.0 * 2000.0 * 2000.0 / 20.0 + 2020; counter += (5 - 1) / 4) { temp = counter / 1979;& [/ T3 I) N' h0 r0 ~( K9 N result = counter; " {( @/ \6 j. S. Q3 E }! o) p7 \* J) ?1 T printf("Result is %lf\n", result);" T$ l& j6 @. E) g: o return 0;# f2 `9 n0 e7 ~& h/ A } T$ c- I! r8 d$ m1 l 首先不加任何优化选项进行编译:2 Q8 Z7 I$ e) T7 m # gcc -Wall optimize.c -o optimize - R# S4 Z' E Z) T% U# u. O6 z) t$ z 借助Linux提供的time命令,可以大致统计出该程序在运行时所需要的时间:( [) ?: G2 o6 ~ , f4 C7 W% i; V8 j1 T. z ; ~9 x' v' z. p( R2 g # time ./optimize* z. X5 q3 g; u) Y, C Result is 400002019.000000 real 0m14.942s m' [9 I L7 O' X$ H. g3 C* p user 0m14.940s; q- }/ z0 j! E sys 0m0.000s M+ N+ [8 T' k( `* M j! o 接下去使用优化选项来对代码进行优化处理: # gcc -Wall -O optimize.c -o optimize2 R2 p9 V: j- D4 ]: y1 I 在同样的条件下再次测试一下运行时间: # time ./optimize3 k) a) S3 v8 ~. u4 q) n) l Result is 400002019.0000006 H; ^5 p) H. Z; B, z/ B& w8 f real 0m3.256s. b N5 |7 f! R# z8 J- V user 0m3.240s2 d$ r0 o4 {; s R5 L2 w/ F* B9 T sys 0m0.000s % d m2 }; {3 O& e" E 对比两次执行的输出结果不难看出,程序的性能的确得到了很大幅度的改善,由原来的14秒缩短到了3秒。这个例子是专门针对GCC的优化功能而设计的,因此优化前后程序的执行速度发生了很大的改变。尽管GCC的代码优化功能非常强大,但作为一名优秀的Linux程序员,首先还是要力求能够手工编写出高质量的代码。如果编写的代码简短,并且逻辑性强,编译器就不会做更多的工作,甚至根本用不着优化。 优化虽然能够给程序带来更好的执行性能,但在如下一些场合中应该避免优化代码: / B* t0 U7 e3 [/ n z$ z 7 _5 J9 g' n' _+ M f5 B ◆ 程序开发的时候优化等级越高,消耗在编译上的时间就越长,因此在开发的时候最好不要使用优化选项,只有到软件发行或开发结束的时候,才考虑对最终生成的代码进行优化。 9 c- r2 o; a8 I# O ◆ 资源受限的时候一些优化选项会增加可执行代码的体积,如果程序在运行时能够申请到的内存资源非常紧张(如一些实时嵌入式设备),那就不要对代码进行优化,因为由这带来的负面影响可能会产生非常严重的后果。 2 Y; s4 T/ I) o* n' C" J ◆ 跟踪调试的时候在对代码进行优化的时候,某些代码可能会被删除或改写,或者为了取得更佳的性能而进行重组,从而使跟踪和调试变得异常困难。 |
关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )
GMT+8, 2025-8-17 21:14 , Processed in 0.109375 second(s), 27 queries , Gzip On.
地址:深圳市南山区科技生态园2栋A座805 电话:19926409050