#
gcc編譯過(guò)程分為4個(gè)階段:(源代理*.c)
.預(yù)處理(和頭文件*.h)
.適當(dāng)編譯(生成目標(biāo)代理*.o)
.匯編
.鏈接(和引導(dǎo)代碼,庫(kù)代碼)
其他的調(diào)試選項(xiàng)包括-p和-pg,它們將剖析(profiling)信息加入二進(jìn)制文件中.這些信息對(duì)于找出代碼中的性能瓶頸以及開(kāi)發(fā)高性能的程序非常有
幫助.-p選項(xiàng)在代碼中加入prof程序能夠讀取的剖析符合信息,而-pg選項(xiàng)加入了GNU項(xiàng)目中prof的化身gprof能夠解釋的符合信息.-a選項(xiàng)
在代碼中加入代碼塊(比如函數(shù))累計(jì)使用的次數(shù).
-save-temps選項(xiàng)可以保存在編譯過(guò)程中生成的中間文件,其中包括目標(biāo)文件和匯編代碼文件.
如果你對(duì)編譯器到底花費(fèi)了多少時(shí)間來(lái)完成它的工作感興趣,可以考慮使用-Q選項(xiàng),這個(gè)選項(xiàng)讓gcc顯示編譯過(guò)程中碰到的每個(gè)函數(shù),并提供編譯器編譯每個(gè)函數(shù)所花時(shí)間的剖析信息.
$gcc -g hello.c -o hello
$ls -l hello
-rwxr-xr-x 1 kwall users 10275 May
21 23:27 hello
$gcc -ggdb hello.c -o hello
$ls -l hello
-rwxr-xr-x
1 kwall users 8135 May 21 23:28
hello
-g選項(xiàng)讓二進(jìn)制文件大小增長(zhǎng)到將近三倍,而-ggdb選項(xiàng)也讓其大小增加了一倍!盡管會(huì)使文件大小增長(zhǎng),仍然建議在執(zhí)行文件中包含標(biāo)準(zhǔn)的調(diào)試符合(使用-g選項(xiàng)創(chuàng)建),以便某些用戶在遇到問(wèn)題時(shí)可以調(diào)試你的代碼.
gcc優(yōu)化標(biāo)志
選項(xiàng) 作用
-ffloat-store
禁止在CPU的寄存器中保存浮點(diǎn)變量的值.這能把CPU寄存器節(jié)省下來(lái)留作它用,而且可以防止產(chǎn)生過(guò)分精確但不必要的浮點(diǎn)數(shù).
-ffast-math
產(chǎn)生浮點(diǎn)數(shù)學(xué)優(yōu)化,這能提高速度但違反了IEEE或ANSI/ISO標(biāo)準(zhǔn).如果程序不需要嚴(yán)格遵守IEEE規(guī)范,可在編譯浮點(diǎn)密集型的程序時(shí)考慮采用這一標(biāo)志
-finline-functions 把所有的"簡(jiǎn)單"函數(shù)在調(diào)用它們的函數(shù)中就地展開(kāi).編譯器決定了什么是"簡(jiǎn)單"函數(shù).減少處理器與函數(shù)相關(guān)的開(kāi)銷是一種基本的優(yōu)化技術(shù)
-funroll-loops 展開(kāi)所有能在編譯時(shí)確定重復(fù)次數(shù)的循環(huán)體.展開(kāi)循環(huán)體后每步循環(huán)都能省出幾條CPU指令,這樣大大減少了執(zhí)行時(shí)間
-fomit-frame-pointer 如果函數(shù)不需要?jiǎng)t丟掉指針,該指針保存在CPU的一個(gè)寄存器中.因?yàn)槿サ袅嗽O(shè)置,保存和恢復(fù)幀指針?biāo)匦璧闹噶?所以加快了處理速度.
-fschedule-insns 記錄可能暫停的指令,因?yàn)樗鼈冋诘群虻臄?shù)據(jù)不在CPU中
-fschedule-insns2
執(zhí)行第二次指令重排序(類似于-fschedule-insns)
-fmove-all-movables
把所有出現(xiàn)在循環(huán)體內(nèi)部但穩(wěn)定不變的計(jì)算移出循環(huán)體.這從循環(huán)體中去除了不必要的操作,加快了循環(huán)的整體運(yùn)算速度.
內(nèi)聯(lián)和循環(huán)展開(kāi)技術(shù)都能夠大大提高程序的執(zhí)行速度,因?yàn)樗鼈兌急苊饬撕瘮?shù)調(diào)用和變量查找的開(kāi)銷,但付出的代價(jià)往往是大大增加了目標(biāo)或二進(jìn)制代碼的大小.
一般而言,Linux程序員似乎愛(ài)用優(yōu)化選項(xiàng)-O2.
使用gcc的-g和-ggdb選項(xiàng)在編譯后的程序中插入調(diào)試信息以方面調(diào)試會(huì)話過(guò)程.能夠用1,2或3來(lái)限定-g選項(xiàng)來(lái)指定產(chǎn)生多少調(diào)試信息.默認(rèn)的級(jí)別
是2(-g2),此時(shí)的產(chǎn)生的調(diào)試信息包括擴(kuò)展的符號(hào)表,行號(hào)以及局部或外部變量的信息.這些信息全部保存在二進(jìn)制文件里.3級(jí)調(diào)試信息包括所有的2級(jí)信
息和源代碼中定義的所有宏.相反,1級(jí)產(chǎn)生的信息只夠創(chuàng)建回溯(backtrace)和堆棧轉(zhuǎn)儲(chǔ)(stack dump)之用.
回溯是指一個(gè)程序調(diào)用函數(shù)的歷史.堆棧轉(zhuǎn)儲(chǔ)是一個(gè)通常以原始的十六機(jī)制格式保存程序執(zhí)行環(huán)境內(nèi)容的列表,列表內(nèi)容主要是CPU寄存器和分配給程序的內(nèi)存.注意,1級(jí)調(diào)試不產(chǎn)生局部變量和行號(hào)的調(diào)試信息.
計(jì)算pi的平方根
/*
* pisqrt.c - Calculate the square of PI
100,000,000
*/
#include <stdio.h>
#include
<math.h>
int main(void)
{
double pi = M_PI; /* Defined in
<math.h> */
double pisqrt;
long i;
for(i = 0; i <
10000000; ++i) {
pisqrt = sqrt(pi);
}
return
0;
}
pisqrt的執(zhí)行時(shí)間
標(biāo)志/優(yōu)化
平均執(zhí)行時(shí)間
<none>
5.43s
-O1 2.74s
-O2 2.83s
-O3 2.76s
-ffloat-store 5.41s
-ffast-math 5.46s
-funroll-loops
5.44s
-fschedule-insns
5.45s
-fschedule-insns2 5.44s
這個(gè)例子說(shuō)明,除非對(duì)處理器的體系結(jié)構(gòu)非常了解或者知道某種特殊的優(yōu)化專門針對(duì)你的程序有影響,否則就應(yīng)該使用優(yōu)化選項(xiàng)-O.
pgcc的主要好處是它對(duì)Pentium處理器的優(yōu)化較好.
1.關(guān)于可移植性
#ifdef __STRICT_ANSI__
/* use ANSI/ISO C only here
*/
#else
/* use GNU extensions here
*/
#endif
如果用戶或是ANSI兼容的編譯器定義了__STRICT_ANSI__宏,則表明需施加ANSI兼容的環(huán)境,并編譯#ifdef語(yǔ)句塊的第一部分代碼.否則,編譯#else后面的代碼.
2.GNU擴(kuò)展
gcc使用long
long 類型來(lái)提供64位儲(chǔ)存單元:
long long
long_int_var;
內(nèi)聯(lián)函數(shù)
要使用內(nèi)聯(lián)函數(shù),需在函數(shù)的返回類型前面插入關(guān)鍵字inline,如下面的代碼片段所示,還要在編譯時(shí)使用-O優(yōu)化選項(xiàng).
inline
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b
=
tmp;
}
函數(shù)和變量屬性
關(guān)鍵字attribute通過(guò)向gcc指明有關(guān)代碼的更多信息來(lái)幫助代碼優(yōu)化工作進(jìn)行得更好.例如,標(biāo)準(zhǔn)庫(kù)函數(shù)exit和
abort都不返回調(diào)用它們的函數(shù).編譯器如果知道它們不返回就能生成效率稍高的代碼.當(dāng)然用戶程序也能定義不返回的函數(shù).gcc允許為這些函數(shù)指定
noreturn屬性,作為編譯器在優(yōu)化該函數(shù)時(shí)的提示.
例如,假設(shè)有個(gè)沒(méi)有返回的函數(shù)die_on_error.為了使用函數(shù)屬性,可以在函數(shù)聲明后面加上__attribute__((attribute_name)).于是函數(shù)die_on_error的聲明如下:
void
die_on_error(void) __attribute__ ((noreturn));
函數(shù)還和平常一樣來(lái)定義:
#include
<stdlib.h>
void die_on_error(void)
{
/* your code here
*/
exit(EXIT_FAILURE);
}
也可以對(duì)變量指定屬性.例如,aligned屬性指示編譯器在為變量分配內(nèi)存空間時(shí)按指定字節(jié)數(shù)對(duì)齊邊界.下列語(yǔ)句:
int
int_var __attribute__ ((aligned 16)) =
0;
使gcc讓變量int_var的邊界按16字節(jié)對(duì)齊.packed屬性告訴gcc為變量或結(jié)構(gòu)分配最小的內(nèi)存空間.
如果想要關(guān)閉對(duì)未用變量發(fā)出的所有警告,那么可以對(duì)變量使用unused屬性,它告訴編譯器該變量不準(zhǔn)備使用.下面的變量聲明會(huì)消除警告:
float
big_salary __attribute__
((unused));
使用case區(qū)間
case區(qū)間是一個(gè)非常有用的擴(kuò)展.其語(yǔ)法如下:
case LOWVAL ...
LOWVAL:
注意,在省略號(hào)前后必須有空格.在switch語(yǔ)句中,case區(qū)間指定了落在LOWVAL和HIVAL區(qū)間內(nèi)的那些整數(shù)值.例如:
switch(int_var)
{
case 0 ... 2:
/* your code here */
break;
case 3 ...
5:
/* more code here */
break;
default:
/* default
code here
*/
}
構(gòu)造函數(shù)名稱
把函數(shù)名用作字符串是GNU的擴(kuò)展,它能極大地簡(jiǎn)化調(diào)試工作.gcc預(yù)先定義了變量__FUNCTION__為當(dāng)前函數(shù)(控制流程當(dāng)前所在的位置)的名字,就好像它被寫在源代碼里去了一樣.
使用__FUNCTION__變量
/*
* showit.c
- Illustrate using the __FUNCTION__ variable
*/
#include
<stdio.h>
void foo(void);
int
main(void)
{
printf("The current function is %s\n",
__FUNCTION__);
foo();
return 0;
}
void
foo(void)
{
printf("The current function is %s\n", __FUNCTION__);
}
下面是定義變量的一般方法:
VARNAME=some_text
[...]
把變量用括號(hào)起來(lái),并在前面加上"$"符號(hào),就可以引用變量的值:
$(VARNAME)
變量一般都在makefile的頭部定義,并且,按照慣例,所有的makefile變量都應(yīng)該大寫.
在makefile中使用變量
OBJS
= howdy.o helper.o
HDRS = helper.h
howdy: $(OBJS) $(HDRS)
gcc $(OBJS) -o howdy
helper.o: helper.c $(HDRS)
gcc -c
helper.c
howdy.o: howdy.c
gcc -c howdy.c
hello: hello.c
gcc
hello.c -o hello
all: howdy hello
clean:
rm howdy hello
*.o
make使用兩種變量:遞歸展開(kāi)變量和簡(jiǎn)單展開(kāi)變量.遞歸展開(kāi)變量在引用時(shí)逐層展開(kāi),即如果在展開(kāi)式中包含了對(duì)其他變量的引用,則這些變量也將被展開(kāi),直到?jīng)]有需要展開(kāi)的變量為止,這就是所謂的遞歸展開(kāi).
考慮下面的變量定義:
CC
= gcc
CC = $(CC)
-o
CC在被引用時(shí)遞歸展開(kāi),從而陷入一個(gè)無(wú)限循環(huán)中:CC將展開(kāi)為$(CC)的值,從而永遠(yuǎn)也讀不到-o選項(xiàng).
為了避免這個(gè)問(wèn)題,可以使用簡(jiǎn)單展開(kāi)變量.與遞歸展開(kāi)變量在引用時(shí)展開(kāi)不同,簡(jiǎn)單展開(kāi)變量在定義處展開(kāi),并且只展開(kāi)一次,從而取消了變量的嵌套引用.在定義時(shí),其語(yǔ)法與遞歸展開(kāi)變量有細(xì)微的不同:
CC
:= gcc -o
CC += -O2
第一個(gè)定義使用":="設(shè)置CC的值為gcc -o,
第二個(gè)定義使用"+="在前面定義的CC后附加了-O2,從而CC最終的值是gcc -o
-O2.
除用戶定義變量外,make也允許使用環(huán)境變量,自動(dòng)變量和預(yù)定義變量.使用環(huán)境變量非常簡(jiǎn)單.在啟動(dòng)時(shí),make讀取已定義的環(huán)境變量,并且創(chuàng)建與之同名同值的變量.但是,如果makefile中有同名的變量,則這個(gè)變量將取代與之相應(yīng)的環(huán)境變量,所以應(yīng)當(dāng)注意這一點(diǎn).
自動(dòng)變量
變量
說(shuō)明
$@
規(guī)則的目標(biāo)所對(duì)應(yīng)的文件名
$<
規(guī)則中的第一個(gè)相關(guān)文件名
$^
規(guī)則中所有相關(guān)文件的列表,以空格為分界符
$?
規(guī)則中日期新于目標(biāo)的所有相關(guān)文件的列表,以空格為分隔符
$(@D)
目標(biāo)文件的目錄部分(如果目標(biāo)在子目錄中)
$(@F)
目標(biāo)文件的文件名部分(如果目標(biāo)在子目錄中)
用于文件名和標(biāo)志的預(yù)定義變量
變量
說(shuō)明
AR
歸檔維護(hù)程序,默認(rèn)值=ar
AS
匯編程序,默認(rèn)值=as
CC
C編譯程序,默認(rèn)值=cc
CPP
C預(yù)處理程序,默認(rèn)值=
cpp
RM
文件刪除程序,默認(rèn)值="rm -f"
ARFLAGS
傳給歸檔維護(hù)程序的標(biāo)志,默認(rèn)值=rv
ASFLAGS
傳給匯編程序的標(biāo)志,沒(méi)有默認(rèn)值
CFLAGS
傳給C編譯器的標(biāo)志,沒(méi)有默認(rèn)值
CPPFLAGS
傳給C預(yù)處理程序的標(biāo)志,沒(méi)有默認(rèn)值
LDFLAGS
傳給鏈接程序(ld)的標(biāo)志,沒(méi)有默認(rèn)值
下面解釋make是如何工作的:當(dāng)遇到目標(biāo)體clean時(shí),make先查看其是否有依賴體,因?yàn)閏lean沒(méi)有依賴體,所以make認(rèn)為目標(biāo)體是最新的而不執(zhí)行任何操作.為了編譯這個(gè)目標(biāo)體,必須輸入make
clean.
然而,如果恰巧有一個(gè)名為clean的文件存在,make就會(huì)發(fā)現(xiàn)它.然后和前面一樣,因?yàn)閏lean沒(méi)有依賴體文件,make就認(rèn)為這個(gè)文件是最新的而不會(huì)執(zhí)行相關(guān)命令.為了處理這類情況,需要使用特殊的make目標(biāo)體.PHONY.
.PHONY的依賴體文件的含義和通常一樣,但是make不檢查是否存在有文件名和依賴體中的一個(gè)名字相匹配的文件,而是直接執(zhí)行與之相關(guān)的命令.在使用了.PHONY之后,前面的例子如下:
howdy:
howdy.o helper.o helper.h
gcc howdy.o helper.o -o howdy
helper.o:
helper.c helper.h
gcc -c helper.c
howdy.o: howdy.c
gcc -c
howdy.c
hello: hello.c
gcc hello.c -o hello
all: howdy
hello
.PHONY : clean
clean:
rm howdy hello *.o
|