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