標籤:target get 記錄 val 它的 預設 選項 副檔名 root
在程式設計中,檔案包含是很有用的。一個大的程式可以分為多個模組,由多個程式員分別編程。有 些公 用的符號常量或宏定義等可單獨組成一個檔案,在其它檔案的開頭用包含命令包含該檔案即可使 用。這樣,可避免在每個檔案開頭都去書寫那些公用量,從而節省時間,並減少出錯。
對檔案包含命令還要說明以下幾點:
1. 包含命令中的檔案名稱可以用雙引號括起來,也可以用角括弧括起來。例如以下寫法都是允許的:
#include"stdio.h"
#include
但是這兩種形式是有區別的:使用角括弧表示在包含檔案目錄中去尋找(包含目錄是由使用者在設定環境時 設定的),而不在源檔案目錄去尋找;
使用雙引號則表示首先在當前的源檔案目錄中尋找,若未找到才到包含目錄中去尋找。使用者編程時可根據 自己檔案所在的目錄來選擇某一種命令形式。
2. 一個include命令只能指定一個被包含檔案,若有多個檔案要包含,則需用多個include命令。
3. 檔案包含允許嵌套,即在一個被包含的檔案中又可以包含另一個檔案。
1.include<標頭檔名>和include"標頭檔名"
如:include和include"stdio.h"
前者(使用<>),來引用stdio.h檔案,是首先檢索標準路徑,看看這些檔案夾下是否有該標頭檔;如果沒有,也不會檢索當前檔案所在路徑,並將報錯。
後者(使用""),來引用stdio.h檔案,是首先檢索檔案的當前路徑;如果沒有,再檢索標準路徑,看看這些檔案夾下是否有該標頭檔。
2.linux下,上述標準路徑有:/usr/include,/usr/local/include。
3.。如,等。其中,前面的字串(如sys,net)表示標準路徑下的檔案夾名,後面的字串(如io.h,ethernet.h),表示在linux標準路徑下的各檔案夾下的標頭檔名,如sys檔案夾下的io.h檔案,即我們可以在/usr/include/sys目錄下發現io.h檔案。
linux博大精深,需要慢慢積累。
4.如果想在指定路徑下檢索標頭檔,可加選項-I。如我的/home/Desktop目錄下有個標頭檔local1.h,在編譯包含local1.h的test.c檔案時,可用:gcc test.c -o test -I /root/Desktop。
一、討論環境
*作業系統:Redhat5/Fedora14
*編譯器:gcc 4.5.1
以下言論僅確保在以上環境中適用。別的環境,大家可以通過類比方法,得到啟示。
二、C語言標頭檔的尋找路徑
C語言,使用include指令,包含標頭檔,但又細分兩種形式:
1、形式一:#include “file1”
gcc先在目前的目錄(指包含本條#include指令的源檔案所在的目錄)尋找file1,如果找不到,繼續在由-iquote選項(如果有的話)指定的目錄中尋找file1。
例如,在檔案/usr/include/sys/stat.h中,包含指令#include “types.h”,那麼gcc先在/usr/include/sys目錄下尋找types.h檔案。嗯,在該目錄下,確實存在一個types.h的檔案。現假設我們把這個檔案移動到另一個目錄:mv /usr/include/sys/types.h /bar/foo/,我們在編譯時間,可以通過-iquote選項,在不改變stat.h的情況下,正常編譯(當然,通常不建議這樣做):
gcc -iquote /bar/foo -I/usr/include/sys *.o
2、形式二:#include
gcc按照以下順序尋找file2:
-Idir1 -Idir2 ...
/usr/local/include
libdir/gcc///include
/usr//include
/usr/include
第一行中,-Idir1 -Idir2 ... 是使用者通過gcc的-I選項指定的目錄。值得一提的是,放在/usr/local/include/下的標頭檔也會被gcc自動的檢索,這與/usr/local/lib/目錄下的庫處理方式是不一樣的(gcc的連結器在運行時階段不會自動尋找該目錄下的庫檔案,下一節會提到)。
三、C語言庫檔案的尋找路徑
C語言庫檔案的尋找路徑,又分為兩個階段:連結階段、運行時階段。
1、連結階段(link time)
此階段,需要告訴編譯器,在哪裡找到庫檔案?以靜態還是動態方式連結庫檔案?預設情況下使用動態方式連結,這要求存在對應的.so動態庫檔案,如果不存在,則尋找相應的.a靜態庫檔案。若在編譯時間向gcc傳入-static選項,則使用靜態方式連結,這要求所有庫檔案都必須有對應的*.a靜態庫。
那麼,是否可以令某些庫使用動態連結,另一些庫使用靜態連結?不太確定,請參考ld的使用手冊,我沒有這樣用過。
切入正題,在連結階段,gcc編譯器如何尋找庫檔案呢(linker本身並沒有預設的尋找路徑,這些尋找路徑是由gcc傳遞給linker的)?大家可以在編譯時間,向gcc加入-v選項來觀察它向linker傳遞的庫檔案尋找路徑(觀察LIBRARY_PATH變數的值),通常尋找路徑如下:
任何由-rpath-link或-rpath選項指定的目錄
LD_RUN_PATH(如果沒有找到-rpath或-rpath-link選項)
-Ldir1 -Ldir2 ...
/usr/lib/gcc///
/usr/lib/
第一行-rpath-link與-rpath選項的區別在於,-rpath選項指定的目錄被寫入程式碼到可執行檔中,-rpath-link選項指定的目錄只在連結階段生效。由於這兩個選項都是連結器ld的選項,如何從gcc中向ld傳遞這兩個選項?方法如下(更從細節參考gcc的-Wl選項):
gcc -Wl, -rpath, /usr/local/lib
這相當於向ld向傳遞了如下參數:
ld -rpath /usr/local/lib
第二行,如果沒有設定-rpath或-rpath-link選項,則尋找LD_RUN_PATH環境變數指定的目錄,並把它當作-rpath選項來處理。第三行-Ldir1 -Ldir2 ...,是我們通過gcc的-L選項向其指定的庫檔案尋找路徑,尋找順序按照我們傳遞的-L參數從左至右進行搜尋;第四行屬於gcc自己的庫目錄;第五行/usr/lib/是Linux系統預設的系統庫檔案的目錄。第四、第五行,都是gcc自動向linker傳遞的尋找目錄。例如我現在的機器上,使用gcc -v可以看到LIBRARY_PATH變數值為:
LIBRARY_PATH=/usr/lib/gcc/i686-redhat-linux/4.5.1/:/usr/lib/
但是這並不是全部真理!根據我自己的測試,我發現gcc會把/usr/local/lib/目錄也作為連結階段的尋找路徑,這正是問題的根源——我們在連結過程中,使用到了/usr/local/lib/裡面的一些庫檔案,但在運行時階段,卻說找不到該庫檔案。
2、運行時階段(runtime)
僅當可執行程式採用動態方式連結庫檔案時,才會存在執行階段程式庫檔案的尋找問題。對於這種可執行程式,它本身只是記錄動態庫的名稱。所以在運行該程式時,作業系統的載入程式(ld.so)需要根據庫的名稱,在必要時載入庫檔案到記憶體中。
在linux中,在運行時階段,動態庫(又叫共用庫)的尋找路徑如下:
-rpath選項指定的目錄(已被寫入程式碼到可執行檔中)
LD_LIBRARY_PATH
/lib或/usr/lib
系統預設的尋找路徑
我們可以通過readelf查看被寫入程式碼到可執行檔中的rpath:
$ readelf -d <可執行檔名> #Display the dynamic section (if present)
LD_LIBRARY_PATH則沒有這個問題,但是通常我們不建議使用這個環境變數,因為修改這個變數意味著影響所有依賴於這個環境變數的程式(如果非要使用,請把這個環境變數寫在啟動指令碼中,並且讓它隻影響指令碼中的程式)。
那麼系統預設的尋找路徑又是怎樣的?在Redhat5/Fedora14中,ld.so通過讀取/etc/ld.so.cache檔案來尋找庫檔案的位置,如果沒有找到則繼續從/etc/ld.so.conf檔案中指定的目錄尋找。這個ld.so.cache檔案相當於一個key-value的資料庫,key就是動態庫的名稱,value就是這些庫的存放路徑。
那麼/etc/ld.so.cache檔案是怎麼產生的呢?這就要談到ldconfig這個工具程式了。ldconfig是動態連結程式庫的組態工具,使用它可以更新/etc/ld.so.cache檔案,也可以查看這個檔案中的key-value資訊(使用ldconfig -p),ldconfig的使用細節,請參考它的使用手冊。總結一下系統預設的尋找路徑:
/etc/ld.so.cache
/etc/ld.so.conf檔案中指定的目錄
四、參考資料
man ld
man ldconfig
http://gcc.gnu.org/onlinedocs/cpp/Search-Path.html
http://www.eyrie.org/~eagle/notes/rpath.html
本文介紹在linux中標頭檔的搜尋路徑,也就是說你通過include指定的標頭檔,linux下的gcc編譯器它是怎麼找到它的呢。在此之前,先瞭解一個基本概念。
標頭檔是一種文字檔,使用文字編輯器將代碼編寫好之後,以副檔名.h儲存就行了。標頭檔中一般放一些重複使用的代碼,例如函式宣告、變數聲明、常數定義、宏的定義等等。當使用#include語句將標頭檔引用時,相當於將標頭檔中所有內容,複製到#include處。#include有兩種寫法形式,分別是:
#include <> : 直接到系統指定的某些目錄中去找某些標頭檔。
#include “” : 先到源檔案所在檔案夾去找,然後再到系統指定的某些目錄中去找某些標頭檔。
#include檔案可能會帶來一個問題就是重複應用,如a.h引用的一個函數是某種實現,而b.h引用的這個函數卻是另外一種實現,這樣在編譯的時候將會出現錯誤。所以,為了避免因為重複引用而導致的編譯錯誤,標頭檔常具有:
#ifndef LABEL
#define LABEL
//代碼部分
#endif
的格式。其中LABEL為一個唯一的標號,命名規則跟變數的命名規則一樣。常根據它所在的標頭檔名來命名,例如,如果標頭檔的檔案名稱叫做hardware.h,那麼可以這樣使用:
#ifndef __HARDWARE_H__
#define __HARDWARE_H__
//代碼部分
#endif
這樣寫的意思就是,如果沒有定義__HARDWARE_H__,則定義__HARDWARE_H__,並編譯下面的代碼部分,直到遇到#endif。這樣當重複引用時,由於__HARDWARE_H__已經被定義,則下面的代碼部分就不會被編譯了,這樣就避免了重複定義。
一句話,標頭檔事實上只是把一些常用的命令整合在裡面,你要用到哪方面的命令就載入哪個標頭檔就可以了。
gcc尋找標頭檔的路徑(按照1->2->3的順序)
1. 在gcc編譯源檔案的時候,通過參數-I指定標頭檔的搜尋路徑,如果指定路徑有多個路徑時,則按照指定路徑的順序搜尋標頭檔。命令形式如:“gcc -I /path/where/theheadfile/in sourcefile.c“,這裡源檔案的路徑可以是絕對路徑,也可以是相對路徑。eg:
設當前路徑為/root/test,include_test.c如果要包含標頭檔“include/include_test.h“,有兩種方法:
1) include_test.c中#include “include/include_test.h”或者#include "/root/test/include/include_test.h",然後gcc include_test.c即可
2) include_test.c中#include <include_test.h>或者#include <include_test.h>,然後gcc –I include include_test.c也可
2. 通過尋找gcc的環境變數C_INCLUDE_PATH/CPLUS_INCLUDE_PATH/OBJC_INCLUDE_PATH來搜尋標頭檔位置。
3. 再找內定目錄搜尋,分別是
/usr/include
/usr/local/include
/usr/lib/gcc-lib/i386-linux/2.95.2/include
最後一行是gcc程式的庫檔案地址,各個使用者的系統上可能不一樣。
gcc在預設情況下,都會指定到/usr/include檔案夾尋找標頭檔。
gcc還有一個參數:-nostdinc,它使編譯器不再系統預設的標頭檔目錄裡面找標頭檔,一般和-I聯合使用,明確限定標頭檔的位置。在編譯驅動模組時,由於非凡的需求必須強制GCC不搜尋系統預設路徑,也就是不搜尋/usr/include要用參數-nostdinc,還要自己用-I參數來指定核心標頭檔路徑,這個時候必須在Makefile中指定。
4. 當#include使用相對路徑的時候,gcc最終會根據上面這些路徑,來最終構建出標頭檔的位置。如#include 就是包含檔案/usr/include/sys/types.h
(轉) C語言標頭檔、庫檔案的尋找路徑