|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
GCC(GNU Compiler Collection)是Linux下最常用的C语言编译器,是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。同时它可以通过不同的前端模块来支持各种语言,如Java、Fortran、Pascal、Modula-3和Ada等。
& v* L; Z4 y1 k; s F. I2 g- I穿插一个玩笑: GNU意思是GNU’s not Unix而非角马。然而GNU还是一个未拆分的连词,这其实是一个源于hacker的幽默:GNU是一个回文游戏,第一个字母G是凑数的,你当然可以叫他做ANU或者BNU。下面开始。+ `2 p; }+ m7 p; Y4 B
8 x: X* y* U( x
一.CC编译程序过程分四个阶段% h- I7 p$ h3 s3 V y
4 \* X0 V5 x+ V: s% F/ k. s6 U
◆ 预处理(Pre-Processing)
& z- Q9 A* A* ?9 a/ a◆ 编译(Compiling)/ ~3 |) j% W+ K* x4 A6 _3 H" v
◆ 汇编(Assembling)-
* m) |* t2 D/ T7 t7 u5 _◆ 链接(Linking)
* h; a+ n1 N! U4 k5 R6 vLinux程序员可以根据自己的需要让GCC在编译的任何阶段结束转去检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。如同其他的编译器,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。
* ~0 W, f# j2 p9 F, s5 k R& A; j- D9 Q; a
GCC提供了30多条警告信息和三个警告级别,使用它们有助于增强程序的稳定性和可移植性。此外,GCC还对标准的C和C++语言进行了大量的扩展,提高程序的执行效率,有助于编译器进行代码优化,能够减轻编程的工作量。# G7 a# F" B9 D4 ^
N/ {: X& @% ^! {) c- U! s% T0 }* \
二.简单编译命令
$ B# {! x: u- u1 v" r1 O8 T! Z' k$ H' z3 X8 A- a' p6 y+ }# u
我们以Hello world程序来开始我们的学习。代码如下:, @* t/ u- z b
* R$ e7 x v8 \3 F* `% B/* hello.c */2. L- N0 u7 |, h, e& g( Y
- _9 E m( c/ d7 |3 v) z#include
9 s5 R! q# [% g0 r) j$ ^; }
/ y6 @' x! _1 Y* dint main(void); a, h3 ~ G) t0 S
% I( F& y3 R' A: C" W- P
{,
* i% \2 [9 ?1 b) x0 A: k' b U$ Z1 D$ P& o6 U! c, U; f2 w) S3 d
printf ("Hello world!\n");*
6 Z: @9 N1 n: v3 |4 Z1 E) G3 ~7 r. Z2 t' E7 X
return 0;
8 ^/ R2 }5 Q3 Y. r$ ]
' n% ^! y! m. ~9 [* X) P) X$ c}
1 `! D( S. }4 o* i5 d$ E6 }
4 I2 s$ E# I8 L1. 执行如下命令:$ gcc -o hello hello.c
) Y: f5 h: M( n1 B" [, d& b. O
- w8 c0 c, f/ r8 {, H运行如下 : $ ./hello& S' p$ p! `* Y% _5 @ U/ c
& U1 a% y5 {/ Y/ K6 j/ H输出: Hello,world!
x5 B8 m4 I. U; E2 |, [# ?
0 j6 t* x L! [& q8 e2. 我们也可以分步编译如下:
/ J. Y7 x7 I4 D- f% W7 k* \# z, y
; b! n) H8 a) ~9 U
(1) $ gcc –E hello.c -o hello.i5 K3 n4 {7 d) Y, [% D Y* C- I. w/ y+ m! R
" G# e$ ^! R3 _' J) ]% K0 o
2 b2 Z0 G( l5 z0 I* l: p6 x: D i
5 P* o6 V5 h2 S# ]' m+ G//预处理结束
& i/ ~9 F. v! ~5 l. @+ f
! z/ L& A5 m- C% l) T2 ]% g6 }
6 C" [) D- _! `1 Y, b" X//这时候你看一下hello.i ,可以看到插进去了很多东西。: f! Y# U7 }3 t" T8 ~3 w0 M B! T* c9 g( c( s# K3 S
( a% A- x7 W. Z9 j2 G7 q
% \+ g' F: R& P/ G: V
(2) $ gcc –S hello.i, p) j* r( W1 q4 |; F4 |, X
- {9 e( n( R [5 d$ w# U: k0 @% w7 C) b: F) s
/ h1 ?; q* T8 n3 i
# M+ D% ]( {- W9 a/ C9 j//生成汇编代码后结束
+ g {! Y: \7 a: P1 ]# o( S2 C' z3 g- n6 c6 Z' G0 E8 d: ~5 e. [$ o7 U2 z% V( C
7 {4 A4 q$ `2 q& s4 Z9 W! L1 r* m! P
(3) $ gcc –c hello.c& B& H7 Z- z+ D+ G1 F
) d7 N5 B1 z# r5 _5 q0 m s6 Y" K4 o
7 `! _8 S4 Z9 \
或者:6 Q' v' p9 J5 V6 E7 S
6 f) `/ c6 Z/ z
: y9 N9 t j5 I
$ gcc -c hello.c –o hello.o2 F; T/ D+ u; Y0 A- L6 T# s0 n, C
/ F8 \. j9 F7 L9 q* V; L n. }9 B! ?* P1 ]1 @( b" M1 |: j. O
& Q- Y9 {0 L* e, I. m# w+ g7 S或者:: o' [2 S& m; _$ U8 F3 H- D& c/ y# P1 Y
, R( x9 t* W/ ~/ x) e9 @" u- w
9 d# _1 e8 l. L6 _* M' C; F K/ g6 E6 g" [* L( S6 k0 g
$ gcc -c hello.i -o hello.o w* F; W0 L& ]' J
U! E- A; j0 a! e) |2 C9 @& U- V5 H2 S# t
2 J) e2 D. q/ _0 f/ s A" {* q: M: c# ^' Q8 x, x' K+ p
9 ~! ?) m6 C4 s @; E7 t' B6 ~//编译结束5 {; s9 O. ]$ i; Q4 o& L/ q+ j+ @4 o$ K+ K; J3 N8 E8 D
+ \" e" Q6 N; ^8 U4 V. y% I$ q
' Z$ o3 B4 \# H, Z5 c: [1 H( ]3 o. e- Z" `% U- _# @
//生成 hello.o文件" S2 a: ~% \6 s" M* @$ p) @( D" `
# f/ j5 q/ ~% u# _7 b# R/ ^8 f& C1 t) A
N" L" Y! @- `5 D: m) y( H) C* L2 h8 ?2 |
(4) $ gcc hello.o –o hello.o0 h/ G/ M) D( A1 {; ?: ~& v$ ?) p, e4 D1 j: k0 y
/ S/ E! u) a) M. R; ] {$ Q7 E }8 g3 B
1 @9 {7 K- f* g- E. p3 j5 T或者:9 x. R; V o! d4 {7 _, x, y8 v( G, c' l3 U
5 Y: n& A P$ c3 d; x& D0 w0 P( P4 w6 k7 s4 x/ t
$ m! L7 k* ~9 R9 ^% ]% h3 W( v4 W, S: `/ o5 I" s+ `3 t% r
$ gcc –o hello hello.c3 V1 P$ E8 x1 f
) \: D2 f; r2 v d& z0 o- o. {% U7 |* o g- B, d
x% j( r+ C. S4 {) x3 T; A# _* @
( a5 A' C$ {) e! H, f! e//链接完毕,生成可执行代码, }& p/ s; O8 S, C1 g- z% T7 U* e: V6 ^7 |. t' B) V/ T; @7 }3 d( W
: o: E- {# N9 [+ C1 z" Y! k( q3 S7 l6 @, D' Z' e- a( x+ P1 E9 y# W0 P
3. 我们可以把几个文件一同编译生成同一个可执行文件。; @! `' _( B: h8 R3 a! r
1 C0 ]3 f1 l4 s8 I( N& t% B* v" z1 H5 t% ]- s# T/ o
0 B, ^- I1 E$ P- P4 A8 g比如:一个工程有main.c foo.c def.c生成foo的可执行文件。
) E# s# [, C; v/ M. H4 u5 W8 l) r
; i. O. j( `4 G& e# X! ^/ O
+ u( z9 x2 u2 Z5 e9 b编译命令如下:
! ]' e; z) V+ p. A& ] L! R7 S4 ]$ u$ q; a( \ o! q( T) z0 o( y, e
3 q! R; ]: |9 H( A$ gcc –c main.c foo.c def.c –o foo% C x9 h' c% Z3 ]/ _/ }( v( G1 F3 ] J& I# B+ c4 ~
, h! R" ]/ n6 l0 [/ |0 p* Y+ {2 g! o [+ n' j& C
3 z. F8 i8 _8 e2 S4 Z0 U, I* C4 O7 |
或者:2 d* b8 `" @; G9 g' a! e* T5 m* V* g4 J8 ^% E3 S- J2 J
( E( S$ @6 m# Z, O
4 g* g: I0 F0 s" @6 [( k" b& {& N* A6 t2 }9 x, ^# {. {" F' M
$ gcc –o foo main.c foo.c def.c3 Z- e, o `: N+ F! [' |
, x$ T) c) H" {) v- m/ r' j' x/ c/ U6 ]" j7 E" o" y
6 R* ?" N( K# F# ] _- }& R- \/ z. \2 n/ a+ R
三.库依赖% U6 I# v% q- b" F; j5 R9 A7 ^4 S5 }3 H
" G0 o1 Y# t$ f# I
( g; U, n" v: Y$ @: u6 W
. n i7 G' Y K8 X4 P$ o函数库是一些头文件(.h)和库文件(.so或者.a)的集合。Linux下的大多数函数都默认将头文件放到/usr/include/目录下,而库文件则放到/usr/lib/目录下,但并非绝对如此。因此GCC设有添加头文件和库文件的编译选项开关。* x* v5 {$ R0 V3 z0 q' v& C; R; _) m7 b. j% `
: s8 b$ r. i7 E+ P
" u6 X' m, B8 m. i. X* [, h0 X h5 o* b2 R+ ~2 s1 S$ f
1. 添加头文件:-I, y$ [1 }( z/ R E; @! T
$ u! ]. ]8 f( |# D5 I4 X- w5 O+ K9 Y1 _8 e
, R7 f/ ~' i: I
& _6 a% v, K. |0 R' B1 o6 X例如在/home/work/include/目录下有编译foo.c所需头文件def.h,为了让GCC能找到它们,就需要使用-I选项:
, h9 c" O- U7 b( h! F( a& P6 U3 w1 L7 [6 q' Y. H
/ s R) |3 i% R4 s$ v1 w/ m8 D5 K! g8 D( J. v
0 z+ H7 T/ _- _) N( m( V% k$ gcc foo.c -I /home/work/include/def.h -o foo
: M$ X3 @ a+ Z+ F* }7 K) [8 n3 |- k
5 s' b. |& ^4 c5 ?. `# @- f# V0 v+ V- }5 j! g& V
; l: B, E2 K$ m: v+ o" ~2. 添加库文件:-L$ x I o0 g5 v) T5 f1 O2 h! H- o1 K$ r5 N0 l
( {+ I- Q+ b2 k1 d$ ?$ W$ o$ \( @" x: T, A4 {4 k
例如在/home/work/lib/目录下有链接所需库文件libdef.so,为了让GCC能找到它们,就需要使用-L选项:% b* p3 Z" v0 k8 t4 b8 [. ^$ ^
* ~4 d% L7 \! ^; I; E$ }. Y& ^& C) o5 }0 ]+ Y( p% `: z4 L* h% k2 Q- n* G% [0 ?' M
( ^- U* P( z" n1 R$ b9 D6 @$ gcc foo.c –L /home/work/lib –ldef.a –o foo
* Y6 J. T! ?, G7 C# ?7 C4 c! Y% ~7 Y. s- g
6 M9 G- r9 |) O) @说明:-l选项指示GCC去连接库文件libdef.so。Linux下的库文件命名有一个约定,即库文件以lib三个字母开头,因为所有的库文件都遵循这个约定,故在用-l选项指定链接的库文件名时可以省去lib三个字母。, {! A0 \% b3 R! u# A5 I
6 W1 Q0 e6 D4 |$ {( ~2 w9 E D' j9 J7 n7 r5 @# p: u, K
3 F5 S a: t) P5 e' w0 [
) Y( \5 N, @6 E; L& [题外语:' b; u( ~# x2 t
/ Q, I) I/ v {* D2 J) S1 x4 t3 x0 w' X) S3 u
' J* b4 R8 m) p! S/ ~$ R! C2 w- B }; d6 v9 E
Linux下的库文件分为动态链接库(.so文件)和静态链接库(.a文件)。GCC默认为动态库优先,若想在动态库和静态库同时存在的时候链接静态库需要指明为-static选项。比如上例中如还有一个libdef.a而你想链接libdef.a时候命令如下:# {: p+ J2 v9 u* Y9 ]5 a/ j5 P" } B2 P# o* E
: c+ t" f0 N2 v3 L
! y& T5 J. y0 Z0 L3 B
0 }9 Z3 z% h: d/ t' P3 ~$ gcc foo.c –L /home/work/lib –static –ldef.a –o foo
5 y! a- D& q1 r
3 V) S/ O) Y3 ]$ J. Q3 o$ `) f% I# E( |7 h
% o9 b+ I- \9 l7 c, N% t四.代码优化6 P: q3 |4 }# U- K7 b8 V1 T0 ^7 l, P: y: l, M& X- e1 h
4 b: G3 y/ P, a" l# B7 C2 y
1 _. O/ [) r G5 Q2 x1 [: q; M( m" ]3 V; H( }! d4 c* u8 y4 i6 H3 c( `0 i' H6 v1 C* G) o! R
GCC提供不同程度的代码优化功能。开关选项是:-On,n取值为0到3。默认为1。-O0表示没有优化,而-O3是最高优化。优化级别越高代码运行越快,但并不是所有代码都能够加载最高优化,而应该视具体情况而定。但一般都使用-O2选项,因为它在优化长度、编译时间和代码大小之间,取得了一个比较理想的平衡点。* X7 O/ n& Q; z6 t1 U2 {4 [" S; d+ ^* z5 I% s: g4 N& I
- T( ?5 G) @! M7 D+ p
- B- r1 c3 v1 A" J, y; T. S- j7 i! `7 q
) M+ u; P3 y; I6 S, t2 Z( ^& n4 T6 E以下这段说的比较详细:* q3 p% P% M& j, \8 ?/ l* t6 _4 T! b4 u9 U8 b8 C! B. j1 }
7 v# _- w, @2 ]
# H7 T" _! x, d' e6 _ U% i6 p( L! R6 I+ N
编译时使用选项-O可以告诉GCC同时减小代码的长度和执行时间,其效果等价于-O1。在这一级别上能够进行的优化类型虽然取决于目标处理器,但一般都会包括线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。选项-O2告诉GCC除了完成所有-O1级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。选项-O3则除了完成所有-O2级别的优化之外,还包括循环展开和其它一些与处理器特性相关的优化工作。通常来说,数字越大优化的等级越高,同时也就意味着程序的运行速度越快。
# F0 u) ?7 v& u- o, M- D# |" v. Q) S! a9 Y z0 A e h: ?
1 B! z, k- A, ?8 b Y: z- W5 ?& d- _& H: G0 e6 K, Z
下面通过具体实例来感受一下GCC的代码优化功能,所用程序如清单3所示。' t7 Z) M1 \' l! U) ~0 a9 Z- v/ m
8 f r) W* M8 Y
- U& G6 D1 p9 w& o% a' T: t `# T+ h% \- G. I0 g: k) A" d/ I9 H3 r0 \
/* optimize.c *// C5 R) y4 a) [- T) i, T
: ^( z* S1 ^2 Q( M" M0 [4 Z- R#include <stdio.h>$ x% z( i2 B% }$ ^
+ b5 {! g/ K. r1 L) b
8 O/ l& W$ w* @, z" N- qint main(void); R1 A4 G2 `0 ~8 r
{' ~3 Q0 g4 {1 [
double counter;# i v+ T5 e, n5 _. L2 ?* }9 j
9 K7 A+ G$ D$ t! P/ Y' @% o' pdouble result;) E" H5 ` u [- P2 v2 k* S. t' t5 t* R* }5 g
double temp;
! m3 j" R4 C+ f+ d1 ^for (counter = 0;
1 C. \( L" b v" q1 u( h" D hcounter < 2000.0 * 2000.0 * 2000.0 / 20.0 + 2020;# N# E5 F$ ^3 ^
counter += (5 - 1) / 4) {6 s4 m m& Z: n( r& _' A% h
temp = counter / 1979;& [/ T3 I) N' h0 r0 ~( K9 N
2 Z0 J& `5 l& c j0 cresult = counter; " {( @/ \6 j. S. Q3 E; q( `- |* L( L6 I6 V3 B( {
}! o) p7 \* J) ?1 T
( D7 U0 z. B4 P; f4 R+ G5 Y( qprintf("Result is %lf\n", result);" T$ l& j6 @. E) g: o2 D0 F l6 G8 W4 P+ F
return 0;# f2 `9 n0 e7 ~& h/ A5 o* S7 s/ w' K G1 `1 j2 T- h
}
3 R4 l) {/ R" w" G% D; u1 w1 `2 y. G# s
T$ c- I! r8 d$ m1 l4 F0 L3 Q% l2 f X' q- X3 J
1 q4 u- X* a6 Z( k' n! Q, f首先不加任何优化选项进行编译:2 Q8 Z7 I$ e) T7 m% G. }" y# z( |7 u
: b; M# {2 Z5 s" \: S/ O% q+ l# F9 @4 q3 Q5 C( @0 w( u
# gcc -Wall optimize.c -o optimize
6 S, V* Q, C3 a4 f1 S" }- R# S4 Z' E Z) T% U# u. O6 z) t$ z
) _9 A) c6 P( M9 a( F4 Q! h( s7 Y p3 {; n/ n
借助Linux提供的time命令,可以大致统计出该程序在运行时所需要的时间:( [) ?: G2 o6 ~
- P3 p6 n' l$ y. q& i6 w( y, f4 C7 W% i; V8 j1 T. z; n' a. T4 {9 P& ^+ ~
; ~9 x' v' z. p( R2 g3 ]- W& |/ u- u1 c2 [" n/ v
# time ./optimize* z. X5 q3 g; u) Y, C
# z8 G* [4 y- W; l, G/ H6 iResult is 400002019.0000001 l2 ~$ O% W4 p( r. k
real 0m14.942s m' [9 I L7 O' X$ H. g3 C* p- ?- Q6 C1 c1 E3 u
user 0m14.940s; q- }/ z0 j! E- y4 e8 ~! z* V
sys 0m0.000s
! x: A0 `- d: k9 s- ?. d$ _7 _( _ M+ N+ [8 T' k( `* M j! o
H! b7 c$ l8 B3 P8 c, L* U; Y- r9 {5 \1 [9 b, F" c6 a4 `
接下去使用优化选项来对代码进行优化处理:
3 {# T ~- F! D! }- I+ j& X% H: Z( K, m7 S: \- N! L% x
k$ U0 A6 K4 R. i3 {2 K
# gcc -Wall -O optimize.c -o optimize2 R2 p9 V: j- D4 ]: y1 I2 ~) R7 w. T2 k+ g
c9 h+ a$ P" S$ Q( |4 _' v5 O, \; G% \, x: ]- S W+ x: C' C
在同样的条件下再次测试一下运行时间:2 w( f' q& L9 U- w& s
8 U5 y; V- J0 c' J- s1 K$ y4 a$ b- h: P
# time ./optimize3 k) a) S3 v8 ~. u4 q) n) l4 M) G+ @* {# V% i
Result is 400002019.0000006 H; ^5 p) H. Z; B, z/ B& w8 f& N5 v4 j7 [, M/ _5 }) j
real 0m3.256s. b N5 |7 f! R# z8 J- V
4 z/ c/ H$ ]0 @user 0m3.240s2 d$ r0 o4 {; s R5 L2 w/ F* B9 T
/ T# V3 ?* q: z2 E, F" Gsys 0m0.000s) n$ S0 N+ v* u1 y$ I
% d m2 }; {3 O& e" E
: u ]& \* Q- m* ?3 s6 G$ X# ?; {* g) Y2 t. U6 x, m" y9 h
对比两次执行的输出结果不难看出,程序的性能的确得到了很大幅度的改善,由原来的14秒缩短到了3秒。这个例子是专门针对GCC的优化功能而设计的,因此优化前后程序的执行速度发生了很大的改变。尽管GCC的代码优化功能非常强大,但作为一名优秀的Linux程序员,首先还是要力求能够手工编写出高质量的代码。如果编写的代码简短,并且逻辑性强,编译器就不会做更多的工作,甚至根本用不着优化。
: l$ r/ X$ D/ F3 q
9 \% S& n& K' L% x$ d1 k优化虽然能够给程序带来更好的执行性能,但在如下一些场合中应该避免优化代码:
% F( V( R$ ]# \ z/ B* t0 U7 e3 [/ n z$ z" u9 w" L8 ^- R5 X. i
7 _5 J9 g' n' _+ M f5 B) S; n3 {: J5 W; X
◆ 程序开发的时候优化等级越高,消耗在编译上的时间就越长,因此在开发的时候最好不要使用优化选项,只有到软件发行或开发结束的时候,才考虑对最终生成的代码进行优化。
4 V! t) {9 q0 o% z1 S: n8 c" @& l9 c- r2 o; a8 I# O6 N. [) g; b4 e! g: w9 u
◆ 资源受限的时候一些优化选项会增加可执行代码的体积,如果程序在运行时能够申请到的内存资源非常紧张(如一些实时嵌入式设备),那就不要对代码进行优化,因为由这带来的负面影响可能会产生非常严重的后果。
( m7 `- e7 ~ J: p1 L! A* _2 Y; s4 T/ I) o* n' C" J
Z7 A! ]1 ^" i3 k1 Q◆ 跟踪调试的时候在对代码进行优化的时候,某些代码可能会被删除或改写,或者为了取得更佳的性能而进行重组,从而使跟踪和调试变得异常困难。
4 l* z+ c: M- r0 H5 N8 {8 b9 d
! r& ^$ J/ d- L" E |
|