在程序設計中,文件包含是很有用的。一個大的程序可以分為多個模塊,由多個程序員分別編程。有 些公 用的符號常量或宏定義等可單獨組成一個文件,在其它文件的開頭用包含命令包含該文件即可使 用。這樣,可避免在每個文件開頭都去書寫那些公用量,從而節省時間,並減少出錯。
對文件包含命令還要說明以下幾點:
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