警告信息php
C語言標準前端
生成特定格式的文件shell
以hello.c爲例子,能夠設置選項生成hello.i, hello.s, hello.o以及最終的hello文件:
hello.c : 最初的源代碼文件;
hello.i : 通過編譯預處理的源代碼;
hello.s : 彙編處理後的彙編代碼;
hello.o : 編譯後的目標文件,即含有最終編譯出的機器碼,但它裏面所引用的其餘文件中函數的內存位置還沒有定義。
hello / a.out : 最終的可執行文件
(還有.a(靜態庫文件), .so(動態庫文件), .s(彙編源文件)留待之後討論)ubuntu
若是你不經過-o指定生成可執行文件名,那麼會默認生成a.out. 不指定生成文件名肯能覆蓋你上次生成的a.out.小程序 e.g. -c生成.o文件時,默認生成與源代碼的主幹同名的.o文件。好比對應hello.c生成hello.o. 但也可在生成目標文件時指定目標文件名(注意同時要給出.o後綴): $gcc -c -o demo.o demo.cide |
$ gcc -Wall -c hello.c : 生成hello.o
$ gcc -Wall -c -save-temps hello.c : 生成hello.i, hello.s, hello.o
注意-Wall 選項的使用場合:僅在涉及到編譯(即會生成.o文件時,用-Wall) 函數
多文件編譯、鏈接oop
若是原文件分佈於多個文件中:file1.c, file2,c
$ gcc -Wall file1.c file2.c -o name優化
若對其中一個文件做了修改,則可只從新編譯該文件,再鏈接全部文件:
$ gcc -Wall -c file2.c
$ gcc file1.c file2.o -c name
注意:若編譯器在命令行中從左向右順序讀取.o文件,則它們的出現順序有限制:含有某函數定義的文件必須出如今含有調用該函數的文件以後。好在GCC無此限制。
編譯預處理
以上述的hello.c爲例, 要對它進行編譯預備處理, 有兩種方法: 在gcc中指定-E選項, 或直接調用cpp.gcc的編譯預處理命令程序爲cpp,比較新版本的gcc已經將cpp集成了,但仍提供了cpp命令. 能夠直接調用cpp命令, 也能夠在gcc中指定-E選項指定它只進行編譯預處理.
$ gcc -E hello.c == $ cpp hello.c
上述命令立刻將預處理結果顯示出來. 不利於觀看. 可採用-c將預處理結果保存:
$ gcc -E -c hello.i hello.c == $ cpp -o hello.i hello.c
注意, -c指定名稱要給出".i"後綴.
另外, gcc針對編譯預處理提供了一些選項:
(1) 除了直接在源代碼中用 #define NAME來定義宏外,gcc可在命令行中定義宏:-DNAME(其中NAME爲宏名), 也可對宏賦值: -DNAME=value 注意等號兩邊不能有空格! 因爲宏擴展只是一個替換過程,也能夠將value換成表達式,但要在兩邊加上雙括號: -DNAME="statement"
e.g. $ gcc -Wall -DVALUE="2+2" tmp.c -o tmp
若是不顯示地賦值,如上例子,只給出:-DVALUE,gcc將使用默認值:1.
(2) 除了用戶定義的宏外, 有一些宏是編譯器自動定義的,它們以__開頭,運行: $ cpp -dM /dev/null, 能夠看到這些宏. 注意, 其中含有不以__開頭的非ANSI宏,它們能夠經過-ansi選項被禁止。
查看宏擴展
1, 運行 $ gcc -E test.c ,gcc對test.c進行編譯預處理,並立馬顯示結果. (不執行編譯) 2, 運行 $gcc -c -save-temps test.c ,不光產生test.o,還產生test.i, test.s,前者是編譯預處理結果, 後者是彙編結果.
利用Emacs查看編譯預處理結果
針對含有編譯預處理命令的代碼,能夠利用emacs方便地查看預處理結果,而不需執行編譯,更爲方便的是,能夠只選取一段代碼,而非整個文件:
1,選擇想要查看的代碼
2,C-c C-e (M-x c-macro-expand)
這樣,就自動在一個名爲"Macroexpansion"的buffer中顯示pre-processed結果.
生成彙編代碼
使用"-S"選項指定gcc生成以".s"爲後綴的彙編代碼:
$ gcc -S hello.c
$ gcc -S -o hello.s hello.c
生成彙編語言的格式取決於目標平臺. 另外, 若是是多個.c文件, 那麼針對每個.c文件生成一個.s文件.
包含頭文件在程序中包含與鏈接庫對應的頭文件是很重要的方面,要使用庫,就必定要能正確地引用頭文件。通常在代碼中經過#include引入頭文件, 若是頭文件位於系統默認的包含路徑(/usr/includes), 則只需在#include中給出頭文件的名字, 不需指定完整路徑. 但若要包含的頭文件位於系統默認包含路徑以外, 則有其它的工做要作: 能夠(在源文件中)同時指定頭文件的全路徑. 但考慮到可移植性,最好經過-I在調用gcc的編譯命令中指定。
下面看這個求立方的小程序(陰影語句表示剛開始不存在):
#include <stdio.h> #include <math.h> int main(int argc, char *argv[]) { double x = pow (2.0, 3.0); printf("The cube of 2.0 is %f/n", x); return 0; } |
使用gcc-2.95來編譯它(-lm選項在後面的鏈接選項中有介紹, 這裏只討論頭文件的包含問題):
$ gcc-2.95 -Wall pow.c -lm -o pow_2.95
pow.c: In function `main':
pow.c:5: warning: implicit declaration of function `pow'
程序編譯成功,但gcc給出警告: pow函數隱式聲明。
$ ./pow_2.95
The cube of 2.0 is 1.000000
明顯執行結果是錯誤的,在源程序中引入頭文件(#include <math.h>),消除了錯誤。
不要忽略Warning信息!它可能預示着,程序雖然編譯成功,但運行結果可能有錯。故,起碼加上"-Wall"編譯選項!並儘可能修正Warning警告。 |
搜索路徑
首先要理解 #include<file.h>和#include"file.h"的區別:
#include<file.h>只在默認的系統包含路徑搜索頭文件
#include"file.h"首先在當前目錄搜索頭文件, 若頭文件不位於當前目錄, 則到系統默認的包含路徑搜索頭文件.
UNIX類系統默認的系統路徑爲:
頭文件,包含路徑: /usr/local/include/ or /usr/include/
庫文件,鏈接路徑: /usr/local/lib/ or /usr/lib/
對於標準c庫(glibc或其它c庫)的頭文件, 咱們能夠直接在源文件中使用#include <file.h>來引入頭文件.
若是要在源文件中引入本身的頭文件, 就須要考慮下面的問題:
1, 若是使用非系統頭文件, 頭文件和源文件位於同一個目錄, 如何引用頭文件呢?
——咱們能夠簡單地在源文件中使用 #include "file.h", gcc將當前目錄的file.h引入到源文件. 若是你很執拗, 仍想使用#include <file.h>語句, 能夠在調用gcc時添加"-I."來將當前目錄添加到系統包含路徑. 細心的朋友可能會想到: 這樣對引用其它頭文件會不會有影響? 好比, #include<file.h>以後緊接着一個#include<math.h>, 它能正確引入math.h嗎? 答案是: 沒有影響. 仍然能正確引用math.h. 個人理解是: "-I."將當前目錄做爲包含路徑的第一選擇, 若在當前目錄找不到頭文件, 則在默認路徑搜索頭文件. 這實際上和#include"file.h"是一個意思.
2, 對於比較大型的工程, 會有許多用戶自定義的頭文件, 而且頭文件和.c文件會位於不一樣的目錄. 又該如何在.c文件中引用頭文件呢?
—— 能夠直接在.c文件中利用#include「/path/file.h", 經過指定頭文件的路徑(能夠是絕對路徑, 也能夠是相對路徑)來包含頭文件. 但這明顯下降了程序的可移植性. 在別的系統環境下編譯可能會出現問題. 因此仍是利用"-I"選項指定頭文件完整的包含路徑.
針對頭文件比較多的狀況, 最好把它們統一放在一個目錄中, 好比~/project/include. 這樣就不需爲不一樣的頭文件指定不一樣的路徑. 若是你嫌每次輸入這麼多選項太麻煩, 你能夠經過設置環境變量來添加路徑: $ C_INCLUDE_PATH=/opt/gdbm-1.8.3/include $ export C_INCLUDE_PATH $ LIBRART_PATH=/opt/gdbm-1.8.3/lib $ export LIBRART_PATH 可一次指定多個搜索路徑,":"用於分隔它們,"."表示當前路徑,如: $ C_INCLUDE_PATH=.:/opt/gdbm-1.8.3/include:/net/include $ LIBRARY_PATH=.:/opt/gdbm-1.8.3/lib:/net/lib (能夠添加多個路徑,路徑之間用:相隔,.表明當前目錄,若.在最前頭,也可省略) 固然,若想永久地添加這些路徑,能夠在.bash_profile中添加上述語句. |
3, 還有一個比較猥瑣的辦法: 系統默認的包含路徑不是/usr/include或/usr/local/include麼? 我把本身的頭文件拷貝到其中的一個目錄, 不就能夠了麼? 的確能夠這樣, 若是你只想在你本身的機器上編譯運行這個程序的話.
前面介紹了三種添加搜索路徑的方法,若是這三種方法一塊兒使用,優先級如何呢?
命令行設置 > 環境變量設置 > 系統默認
與外部庫鏈接
前面介紹瞭如何包含頭文件. 而頭文件和庫是息息相關的, 使用庫時, 要在源代碼中包含適當的頭文件,這樣才能聲明庫中函數的原型(發佈庫時, 就須要給出相應的頭文件).
和包含路徑同樣, 系統也有默認的鏈接路徑:
頭文件,包含路徑: /usr/local/include/ or /usr/include/
庫文件,鏈接路徑: /usr/local/lib/ or /usr/lib/
一樣地, 咱們想要使用某個庫裏的函數, 必須將這個庫鏈接到使用那些函數的程序中.
有一個例外: libc.a或libc.so (C標準庫,它包含了ANSI C所定義的C函數)是不須要你顯式鏈接的, 全部的C程序在運行時都會自動加載c標準庫. |
二者的共同點:
.a, .so都是.o目標文件的集合,這些目標文件中含有一些函數的定義(機器碼),而這些函數將在鏈接時會被最終的可執行文件用到。
二者的區別:
靜態庫.a : 當程序與靜態庫鏈接時,庫中目標文件所含的全部將被程序使用的函數的機器碼被copy到最終的可執行文件中. 靜態庫有個缺點: 佔用磁盤和內存空間. 靜態庫會被添加到和它鏈接的每一個程序中, 並且這些程序運行時, 都會被加載到內存中. 無形中又多消耗了更多的內存空間.
共享庫.so : 與共享庫鏈接的可執行文件只包含它須要的函數的引用表,而不是全部的函數代碼,只有在程序執行時, 那些須要的函數代碼才被拷貝到內存中, 這樣就使可執行文件比較小, 節省磁盤空間(更進一步,操做系統使用虛擬內存,使得一份共享庫駐留在內存中被多個程序使用).共享庫還有個優勢: 若庫自己被更新, 不須要從新編譯與它鏈接的源程序。
靜態庫
下面咱們來看一個簡單的例子,計算2.0的平方根(假設文件名爲sqrt.c):
#include <math.h> #include <stdio.h> int main (void) { double x = sqrt (2.0); printf ("The square root of 2.0 is %f/n", x); return 0; } |
用gcc將它編譯爲可執行文件:
$ gcc -Wall sqrt.c -o sqrt
編譯成功,沒有任何警告或錯誤信息。執行結果也正確。
$ ./sqrt
The square root of 2.0 is 1.414214
下面咱們來看看剛纔使用的gcc版本:
$ gcc --version
gcc (GCC) 4.0.2 20050808 (prerelease) (Ubuntu 4.0.1-4ubuntu9)
如今我用2.95版的gcc把sqrt.c再編譯一次:
$ gcc-2.95 -Wall sqrt.c -o sqrt_2.95
/tmp/ccVBJd2H.o: In function `main':
sqrt.c:(.text+0x16): undefined reference to `sqrt'
collect2: ld returned 1 exit status
編譯器會給出上述錯誤信息,這是由於sqrt函數不能與外部數學庫"libm.a"相連。sqrt函數沒有在程序中定義,也不存在於默認C庫 "libc.a"中,若是用gcc-2.95,應該顯式地選擇鏈接庫。上述出錯信息中的"/tmp/ccVBJd2H.o"是gcc創造的臨時目標文件,用做鏈接時用。
使用下列的命令能夠成功編譯:
$ gcc-2.95 -Wall sqrt.c /usr/lib/libm.a -o sqrt_2.95
它告知gcc:在編譯sqrt.c時,加入位於/usr/lib中的libm.a庫(C數學庫)。
C庫文件默認位於/usr/lib, /usr/local/lib系統目錄中; gcc默認地從/usr/local/lib, /usr/lib中搜索庫文件。(在個人Ubuntu系統中,C庫文件位於/urs/lib中。 |
這裏還要注意鏈接順序的問題,好比上述命令,若是我改爲:
$ gcc-2.95 -Wall /usr/lib/libm.asqrt.c -o sqrt_2.95
gcc會給出出錯信息:
/tmp/cc6b3bIa.o: In function `main':
sqrt.c:(.text+0x16): undefined reference to `sqrt'
collect2: ld returned 1 exit status
正如讀取目標文件的順序,gcc也在命令行中從左向右讀取庫文件——任何包含某函數定義的庫文件必須位於調用該函數的目標文件以後!
指定庫文件的絕對路徑比較繁瑣,有一種簡化方法,相對於上述命令,能夠用下面的命令來替代:
$ gcc-2.95 -Wall sqrt.c -lm -o sqrt_2.95
其中的"-l"表示與庫文件鏈接,"m"表明"libm.a"中的m。通常而言,"-lNAME"選項會使gcc將目標文件與名爲"libNAME.a"的庫文件相連。(這裏假設使用默認目錄中的庫,對於其餘目錄中的庫文件,參考後面的「搜索路徑」。)
上面所提到的"libm.a"就是靜態庫文件,全部靜態庫文件的擴展名都是.a!
$ whereis libm.a
libm: /usr/lib/libm.a /usr/lib/libm.so
正如前面所說,默認的庫文件位於/usr/lib/或/usr/local/lib/目錄中。其中,libm.a是靜態庫文件,libm.so是後面會介紹的動態共享庫文件。
若是調用的函數都包含在libc.a中(C標準庫被包含在/usr/lib/libc.a中,它包含了ANSI C所定義的C函數)。那麼沒有必要顯式指定libc.a:全部的C程序運行時都自動包含了C標準庫!(試試 $ gcc-2.95 -Wall hello.c -o hello)。
共享庫
正由於共享庫的優勢,若是系統中存在.so庫,gcc默認使用共享庫(在/usr/lib/目錄中,庫文件以共享和靜態兩種版本存在)。
運行:$ gcc -Wall -L. hello.c -lNAME -o hello
gcc先檢查是否有替代的libNAME.so庫可用。
正如前面所說,共享庫以.so爲擴展名(so == shared object)。
那麼,若是不想用共享庫,而只用靜態庫呢?能夠加上 -static選項
$ gcc -Wall -static hello.c -lNAME -o hello
它等價於:
$ gcc -Wall hello.c libNAME.a -o hello
$ gcc-2.95 -Wall sqrt.c -static -lm -o sqrt_2.95_static
$ gcc-2.95 -Wall sqrt.c -lm -o sqrt_2.95_default
$ gcc-2.95 -Wall sqrt.c /usr/lib/libm.a -o sqrt_2.95_a
$ gcc-2.95 -Wall sqrt.c /usr/lib/libm.so -o sqrt_2.95_so
$ ls -l sqrt*
-rwxr-xr-x 1 zp zp 21076 2006-04-25 14:52 sqrt_2.95_a
-rwxr-xr-x 1 zp zp 7604 2006-04-25 14:52 sqrt_2.95_default
-rwxr-xr-x 1 zp zp 7604 2006-04-25 14:52 sqrt_2.95_so
-rwxr-xr-x 1 zp zp 487393 2006-04-25 14:52 sqrt_2.95_static
上述用四種方式編譯sqrt.c,並比較了可執行文件的大小。奇怪的是,-static -lm 和 /lib/libm.a爲何有區別?有知其緣由着,懇請指明,在此謝謝了! :)
若是libNAME.a在當前目錄,應執行下面的命令:
$ gcc -Wall -L. hello.c -lNAME -o hello
-L.表示將當前目錄加到鏈接路徑。
利用GNU archiver建立庫
$ ar cr libhello.a hello_fn.o by_fn.o
從hello_fn.o和by_fn.o建立libihello.a,其中cr表示:creat & replace
$ ar t libhello.a
列出libhello.a中的內容,t == table
(也可建立libhello.so)
關於建立庫的詳細介紹,可參考本blog的GNU binutils筆記
調試
通常地,可執行文件中是不包含任何對源代碼的參考的,而debugger要工做,就要知道目標文件/可執行文件中的機器碼對應的源代碼的信息(如:哪條語句、函數名、變量名...). debugger工做原理:將函數名、變量名,對它們的引用,將全部這些對象對應的代碼行號儲存到目標文件或可執行文件的符號表中。
GCC提供-g選項,將調試信息加入到目標文件或可執行文件中。
$ gcc -Wall -g hello.c -o hello
注意:若發生了段錯誤,但沒有core dump,是因爲系統禁止core文件的生成!
$ ulimit -c ,若顯示爲0,則系統禁止了core dump
解決方法:
$ ulimit -c unlimited (只對當前shell進程有效)
或在~/.bashrc 的最後加入: ulimit -c unlimited (一勞永逸)
優化
GCC具備優化代碼的功能,代碼的優化是一項比較複雜的工做,它可歸爲:源代碼級優化、速度與空間的權衡、執行代碼的調度。
GCC提供了下列優化選項:
-O0 : 默認不優化(若要生成調試信息,最好不優化)
-O1 : 簡單優化,不進行速度與空間的權衡優化;
-O2 : 進一步的優化,包括了調度。(若要優化,該選項最適合,它是GNU發佈軟件的默認優化級別;
-O3 : 雞肋,興許使程序速度更慢;
-funroll-loops : 展開循環,會使可執行文件增大,而速度是否增長取決於特定環境;
-Os : 生成最小執行文件;
通常來講,調試時不優化,通常的優化選項用-O2(gcc容許-g與-O2聯用,這也是GNU軟件包發佈的默認選項),embedded能夠考慮-Os。
注意:此處爲O!(非0或小寫的o,-o是指定可執行文件名)。
檢驗優化結果的方法:$ time ./prog
time測量指定程序的執行時間,結果由三部分組成:
real : 進程總的執行時間, 它和系統負載有關(包括了進程調度,切換的時間)
user: 被測量進程中用戶指令的執行時間
sys : 被測量進程中內核代用戶指令執行的時間
user和sys的和被稱爲CPU時間.
注意:對代碼的優化可能會引起警告信息,移出警告的辦法不是關閉優化,而是調整代碼。
警告信息
C語言標準
生成特定格式的文件
以hello.c爲例子,能夠設置選項生成hello.i, hello.s, hello.o以及最終的hello文件:
hello.c : 最初的源代碼文件;
hello.i : 通過編譯預處理的源代碼;
hello.s : 彙編處理後的彙編代碼;
hello.o : 編譯後的目標文件,即含有最終編譯出的機器碼,但它裏面所引用的其餘文件中函數的內存位置還沒有定義。
hello / a.out : 最終的可執行文件
(還有.a(靜態庫文件), .so(動態庫文件), .s(彙編源文件)留待之後討論)
若是你不經過-o指定生成可執行文件名,那麼會默認生成a.out. 不指定生成文件名肯能覆蓋你上次生成的a.out. e.g. -c生成.o文件時,默認生成與源代碼的主幹同名的.o文件。好比對應hello.c生成hello.o. 但也可在生成目標文件時指定目標文件名(注意同時要給出.o後綴): $gcc -c -o demo.o demo.c |
$ gcc -Wall -c hello.c : 生成hello.o
$ gcc -Wall -c -save-temps hello.c : 生成hello.i, hello.s, hello.o
注意-Wall 選項的使用場合:僅在涉及到編譯(即會生成.o文件時,用-Wall)
多文件編譯、鏈接
若是原文件分佈於多個文件中:file1.c, file2,c
$ gcc -Wall file1.c file2.c -o name
若對其中一個文件做了修改,則可只從新編譯該文件,再鏈接全部文件:
$ gcc -Wall -c file2.c
$ gcc file1.c file2.o -c name
注意:若編譯器在命令行中從左向右順序讀取.o文件,則它們的出現順序有限制:含有某函數定義的文件必須出如今含有調用該函數的文件以後。好在GCC無此限制。
編譯預處理
以上述的hello.c爲例, 要對它進行編譯預備處理, 有兩種方法: 在gcc中指定-E選項, 或直接調用cpp.gcc的編譯預處理命令程序爲cpp,比較新版本的gcc已經將cpp集成了,但仍提供了cpp命令. 能夠直接調用cpp命令, 也能夠在gcc中指定-E選項指定它只進行編譯預處理.
$ gcc -E hello.c == $ cpp hello.c
上述命令立刻將預處理結果顯示出來. 不利於觀看. 可採用-c將預處理結果保存:
$ gcc -E -c hello.i hello.c == $ cpp -o hello.i hello.c
注意, -c指定名稱要給出".i"後綴.
另外, gcc針對編譯預處理提供了一些選項:
(1) 除了直接在源代碼中用 #define NAME來定義宏外,gcc可在命令行中定義宏:-DNAME(其中NAME爲宏名), 也可對宏賦值: -DNAME=value 注意等號兩邊不能有空格! 因爲宏擴展只是一個替換過程,也能夠將value換成表達式,但要在兩邊加上雙括號: -DNAME="statement"
e.g. $ gcc -Wall -DVALUE="2+2" tmp.c -o tmp
若是不顯示地賦值,如上例子,只給出:-DVALUE,gcc將使用默認值:1.
(2) 除了用戶定義的宏外, 有一些宏是編譯器自動定義的,它們以__開頭,運行: $ cpp -dM /dev/null, 能夠看到這些宏. 注意, 其中含有不以__開頭的非ANSI宏,它們能夠經過-ansi選項被禁止。
查看宏擴展
1, 運行 $ gcc -E test.c ,gcc對test.c進行編譯預處理,並立馬顯示結果. (不執行編譯) 2, 運行 $gcc -c -save-temps test.c ,不光產生test.o,還產生test.i, test.s,前者是編譯預處理結果, 後者是彙編結果.
利用Emacs查看編譯預處理結果
針對含有編譯預處理命令的代碼,能夠利用emacs方便地查看預處理結果,而不需執行編譯,更爲方便的是,能夠只選取一段代碼,而非整個文件:
1,選擇想要查看的代碼
2,C-c C-e (M-x c-macro-expand)
這樣,就自動在一個名爲"Macroexpansion"的buffer中顯示pre-processed結果.
生成彙編代碼
使用"-S"選項指定gcc生成以".s"爲後綴的彙編代碼:
$ gcc -S hello.c
$ gcc -S -o hello.s hello.c
生成彙編語言的格式取決於目標平臺. 另外, 若是是多個.c文件, 那麼針對每個.c文件生成一個.s文件.
包含頭文件在程序中包含與鏈接庫對應的頭文件是很重要的方面,要使用庫,就必定要能正確地引用頭文件。通常在代碼中經過#include引入頭文件, 若是頭文件位於系統默認的包含路徑(/usr/includes), 則只需在#include中給出頭文件的名字, 不需指定完整路徑. 但若要包含的頭文件位於系統默認包含路徑以外, 則有其它的工做要作: 能夠(在源文件中)同時指定頭文件的全路徑. 但考慮到可移植性,最好經過-I在調用gcc的編譯命令中指定。
下面看這個求立方的小程序(陰影語句表示剛開始不存在):
#include <stdio.h> #include <math.h> int main(int argc, char *argv[]) { double x = pow (2.0, 3.0); printf("The cube of 2.0 is %f/n", x); return 0; } |
使用gcc-2.95來編譯它(-lm選項在後面的鏈接選項中有介紹, 這裏只討論頭文件的包含問題):
$ gcc-2.95 -Wall pow.c -lm -o pow_2.95
pow.c: In function `main':
pow.c:5: warning: implicit declaration of function `pow'
程序編譯成功,但gcc給出警告: pow函數隱式聲明。
$ ./pow_2.95
The cube of 2.0 is 1.000000
明顯執行結果是錯誤的,在源程序中引入頭文件(#include <math.h>),消除了錯誤。
不要忽略Warning信息!它可能預示着,程序雖然編譯成功,但運行結果可能有錯。故,起碼加上"-Wall"編譯選項!並儘可能修正Warning警告。 |
搜索路徑
首先要理解 #include<file.h>和#include"file.h"的區別:
#include<file.h>只在默認的系統包含路徑搜索頭文件
#include"file.h"首先在當前目錄搜索頭文件, 若頭文件不位於當前目錄, 則到系統默認的包含路徑搜索頭文件.
UNIX類系統默認的系統路徑爲:
頭文件,包含路徑: /usr/local/include/ or /usr/include/
庫文件,鏈接路徑: /usr/local/lib/ or /usr/lib/
對於標準c庫(glibc或其它c庫)的頭文件, 咱們能夠直接在源文件中使用#include <file.h>來引入頭文件.
若是要在源文件中引入本身的頭文件, 就須要考慮下面的問題:
1, 若是使用非系統頭文件, 頭文件和源文件位於同一個目錄, 如何引用頭文件呢?
——咱們能夠簡單地在源文件中使用 #include "file.h", gcc將當前目錄的file.h引入到源文件. 若是你很執拗, 仍想使用#include <file.h>語句, 能夠在調用gcc時添加"-I."來將當前目錄添加到系統包含路徑. 細心的朋友可能會想到: 這樣對引用其它頭文件會不會有影響? 好比, #include<file.h>以後緊接着一個#include<math.h>, 它能正確引入math.h嗎? 答案是: 沒有影響. 仍然能正確引用math.h. 個人理解是: "-I."將當前目錄做爲包含路徑的第一選擇, 若在當前目錄找不到頭文件, 則在默認路徑搜索頭文件. 這實際上和#include"file.h"是一個意思.
2, 對於比較大型的工程, 會有許多用戶自定義的頭文件, 而且頭文件和.c文件會位於不一樣的目錄. 又該如何在.c文件中引用頭文件呢?
—— 能夠直接在.c文件中利用#include「/path/file.h", 經過指定頭文件的路徑(能夠是絕對路徑, 也能夠是相對路徑)來包含頭文件. 但這明顯下降了程序的可移植性. 在別的系統環境下編譯可能會出現問題. 因此仍是利用"-I"選項指定頭文件完整的包含路徑.
針對頭文件比較多的狀況, 最好把它們統一放在一個目錄中, 好比~/project/include. 這樣就不需爲不一樣的頭文件指定不一樣的路徑. 若是你嫌每次輸入這麼多選項太麻煩, 你能夠經過設置環境變量來添加路徑: $ C_INCLUDE_PATH=/opt/gdbm-1.8.3/include $ export C_INCLUDE_PATH $ LIBRART_PATH=/opt/gdbm-1.8.3/lib $ export LIBRART_PATH 可一次指定多個搜索路徑,":"用於分隔它們,"."表示當前路徑,如: $ C_INCLUDE_PATH=.:/opt/gdbm-1.8.3/include:/net/include $ LIBRARY_PATH=.:/opt/gdbm-1.8.3/lib:/net/lib (能夠添加多個路徑,路徑之間用:相隔,.表明當前目錄,若.在最前頭,也可省略) 固然,若想永久地添加這些路徑,能夠在.bash_profile中添加上述語句. |
3, 還有一個比較猥瑣的辦法: 系統默認的包含路徑不是/usr/include或/usr/local/include麼? 我把本身的頭文件拷貝到其中的一個目錄, 不就能夠了麼? 的確能夠這樣, 若是你只想在你本身的機器上編譯運行這個程序的話.
前面介紹了三種添加搜索路徑的方法,若是這三種方法一塊兒使用,優先級如何呢?
命令行設置 > 環境變量設置 > 系統默認
與外部庫鏈接
前面介紹瞭如何包含頭文件. 而頭文件和庫是息息相關的, 使用庫時, 要在源代碼中包含適當的頭文件,這樣才能聲明庫中函數的原型(發佈庫時, 就須要給出相應的頭文件).
和包含路徑同樣, 系統也有默認的鏈接路徑:
頭文件,包含路徑: /usr/local/include/ or /usr/include/
庫文件,鏈接路徑: /usr/local/lib/ or /usr/lib/
一樣地, 咱們想要使用某個庫裏的函數, 必須將這個庫鏈接到使用那些函數的程序中.
有一個例外: libc.a或libc.so (C標準庫,它包含了ANSI C所定義的C函數)是不須要你顯式鏈接的, 全部的C程序在運行時都會自動加載c標準庫. |
除了C標準庫以外的庫稱之爲"外部庫", 它多是別人提供給你的, 也多是你本身建立的(後面有介紹如何建立庫的內容).
外部庫有兩種:(1)靜態鏈接庫lib.a
(2)共享鏈接庫lib.so
二者的共同點:
.a, .so都是.o目標文件的集合,這些目標文件中含有一些函數的定義(機器碼),而這些函數將在鏈接時會被最終的可執行文件用到。
二者的區別:
靜態庫.a : 當程序與靜態庫鏈接時,庫中目標文件所含的全部將被程序使用的函數的機器碼被copy到最終的可執行文件中. 靜態庫有個缺點: 佔用磁盤和內存空間. 靜態庫會被添加到和它鏈接的每一個程序中, 並且這些程序運行時, 都會被加載到內存中. 無形中又多消耗了更多的內存空間.
共享庫.so : 與共享庫鏈接的可執行文件只包含它須要的函數的引用表,而不是全部的函數代碼,只有在程序執行時, 那些須要的函數代碼才被拷貝到內存中, 這樣就使可執行文件比較小, 節省磁盤空間(更進一步,操做系統使用虛擬內存,使得一份共享庫駐留在內存中被多個程序使用).共享庫還有個優勢: 若庫自己被更新, 不須要從新編譯與它鏈接的源程序。
靜態庫
下面咱們來看一個簡單的例子,計算2.0的平方根(假設文件名爲sqrt.c):
#include <math.h> #include <stdio.h> int main (void) { double x = sqrt (2.0); printf ("The square root of 2.0 is %f/n", x); return 0; } |
用gcc將它編譯爲可執行文件:
$ gcc -Wall sqrt.c -o sqrt
編譯成功,沒有任何警告或錯誤信息。執行結果也正確。
$ ./sqrt
The square root of 2.0 is 1.414214
下面咱們來看看剛纔使用的gcc版本:
$ gcc --version
gcc (GCC) 4.0.2 20050808 (prerelease) (Ubuntu 4.0.1-4ubuntu9)
如今我用2.95版的gcc把sqrt.c再編譯一次:
$ gcc-2.95 -Wall sqrt.c -o sqrt_2.95
/tmp/ccVBJd2H.o: In function `main':
sqrt.c:(.text+0x16): undefined reference to `sqrt'
collect2: ld returned 1 exit status
編譯器會給出上述錯誤信息,這是由於sqrt函數不能與外部數學庫"libm.a"相連。sqrt函數沒有在程序中定義,也不存在於默認C庫 "libc.a"中,若是用gcc-2.95,應該顯式地選擇鏈接庫。上述出錯信息中的"/tmp/ccVBJd2H.o"是gcc創造的臨時目標文件,用做鏈接時用。
使用下列的命令能夠成功編譯:
$ gcc-2.95 -Wall sqrt.c /usr/lib/libm.a -o sqrt_2.95
它告知gcc:在編譯sqrt.c時,加入位於/usr/lib中的libm.a庫(C數學庫)。
C庫文件默認位於/usr/lib, /usr/local/lib系統目錄中; gcc默認地從/usr/local/lib, /usr/lib中搜索庫文件。(在個人Ubuntu系統中,C庫文件位於/urs/lib中。 |
這裏還要注意鏈接順序的問題,好比上述命令,若是我改爲:
$ gcc-2.95 -Wall /usr/lib/libm.asqrt.c -o sqrt_2.95
gcc會給出出錯信息:
/tmp/cc6b3bIa.o: In function `main':
sqrt.c:(.text+0x16): undefined reference to `sqrt'
collect2: ld returned 1 exit status
正如讀取目標文件的順序,gcc也在命令行中從左向右讀取庫文件——任何包含某函數定義的庫文件必須位於調用該函數的目標文件以後!
指定庫文件的絕對路徑比較繁瑣,有一種簡化方法,相對於上述命令,能夠用下面的命令來替代:
$ gcc-2.95 -Wall sqrt.c -lm -o sqrt_2.95
其中的"-l"表示與庫文件鏈接,"m"表明"libm.a"中的m。通常而言,"-lNAME"選項會使gcc將目標文件與名爲"libNAME.a"的庫文件相連。(這裏假設使用默認目錄中的庫,對於其餘目錄中的庫文件,參考後面的「搜索路徑」。)
上面所提到的"libm.a"就是靜態庫文件,全部靜態庫文件的擴展名都是.a!
$ whereis libm.a
libm: /usr/lib/libm.a /usr/lib/libm.so
正如前面所說,默認的庫文件位於/usr/lib/或/usr/local/lib/目錄中。其中,libm.a是靜態庫文件,libm.so是後面會介紹的動態共享庫文件。
若是調用的函數都包含在libc.a中(C標準庫被包含在/usr/lib/libc.a中,它包含了ANSI C所定義的C函數)。那麼沒有必要顯式指定libc.a:全部的C程序運行時都自動包含了C標準庫!(試試 $ gcc-2.95 -Wall hello.c -o hello)。
共享庫
正由於共享庫的優勢,若是系統中存在.so庫,gcc默認使用共享庫(在/usr/lib/目錄中,庫文件以共享和靜態兩種版本存在)。
運行:$ gcc -Wall -L. hello.c -lNAME -o hello
gcc先檢查是否有替代的libNAME.so庫可用。
正如前面所說,共享庫以.so爲擴展名(so == shared object)。
那麼,若是不想用共享庫,而只用靜態庫呢?能夠加上 -static選項
$ gcc -Wall -static hello.c -lNAME -o hello
它等價於:
$ gcc -Wall hello.c libNAME.a -o hello
$ gcc-2.95 -Wall sqrt.c -static -lm -o sqrt_2.95_static
$ gcc-2.95 -Wall sqrt.c -lm -o sqrt_2.95_default
$ gcc-2.95 -Wall sqrt.c /usr/lib/libm.a -o sqrt_2.95_a
$ gcc-2.95 -Wall sqrt.c /usr/lib/libm.so -o sqrt_2.95_so
$ ls -l sqrt*
-rwxr-xr-x 1 zp zp 21076 2006-04-25 14:52 sqrt_2.95_a
-rwxr-xr-x 1 zp zp 7604 2006-04-25 14:52 sqrt_2.95_default
-rwxr-xr-x 1 zp zp 7604 2006-04-25 14:52 sqrt_2.95_so
-rwxr-xr-x 1 zp zp 487393 2006-04-25 14:52 sqrt_2.95_static
上述用四種方式編譯sqrt.c,並比較了可執行文件的大小。奇怪的是,-static -lm 和 /lib/libm.a爲何有區別?有知其緣由着,懇請指明,在此謝謝了! :)
若是libNAME.a在當前目錄,應執行下面的命令:
$ gcc -Wall -L. hello.c -lNAME -o hello
-L.表示將當前目錄加到鏈接路徑。
利用GNU archiver建立庫
$ ar cr libhello.a hello_fn.o by_fn.o
從hello_fn.o和by_fn.o建立libihello.a,其中cr表示:creat & replace
$ ar t libhello.a
列出libhello.a中的內容,t == table
(也可建立libhello.so)
關於建立庫的詳細介紹,可參考本blog的GNU binutils筆記
調試
通常地,可執行文件中是不包含任何對源代碼的參考的,而debugger要工做,就要知道目標文件/可執行文件中的機器碼對應的源代碼的信息(如:哪條語句、函數名、變量名...). debugger工做原理:將函數名、變量名,對它們的引用,將全部這些對象對應的代碼行號儲存到目標文件或可執行文件的符號表中。
GCC提供-g選項,將調試信息加入到目標文件或可執行文件中。
$ gcc -Wall -g hello.c -o hello
注意:若發生了段錯誤,但沒有core dump,是因爲系統禁止core文件的生成!
$ ulimit -c ,若顯示爲0,則系統禁止了core dump
解決方法:
$ ulimit -c unlimited (只對當前shell進程有效)
或在~/.bashrc 的最後加入: ulimit -c unlimited (一勞永逸)
優化
GCC具備優化代碼的功能,代碼的優化是一項比較複雜的工做,它可歸爲:源代碼級優化、速度與空間的權衡、執行代碼的調度。
GCC提供了下列優化選項:
-O0 : 默認不優化(若要生成調試信息,最好不優化)
-O1 : 簡單優化,不進行速度與空間的權衡優化;
-O2 : 進一步的優化,包括了調度。(若要優化,該選項最適合,它是GNU發佈軟件的默認優化級別;
-O3 : 雞肋,興許使程序速度更慢;
-funroll-loops : 展開循環,會使可執行文件增大,而速度是否增長取決於特定環境;
-Os : 生成最小執行文件;
通常來講,調試時不優化,通常的優化選項用-O2(gcc容許-g與-O2聯用,這也是GNU軟件包發佈的默認選項),embedded能夠考慮-Os。
注意:此處爲O!(非0或小寫的o,-o是指定可執行文件名)。
檢驗優化結果的方法:$ time ./prog
time測量指定程序的執行時間,結果由三部分組成:
real : 進程總的執行時間, 它和系統負載有關(包括了進程調度,切換的時間)
user: 被測量進程中用戶指令的執行時間
sys : 被測量進程中內核代用戶指令執行的時間
user和sys的和被稱爲CPU時間.
注意:對代碼的優化可能會引起警告信息,移出警告的辦法不是關閉優化,而是調整代碼。