程式編譯運行時標頭檔或動態連結程式庫的尋找

來源:互聯網
上載者:User

          轉載請註明來源:http://blog.csdn.net/dlutxie/article/details/6776936

          當考慮怎樣總結這個標頭檔及動態連結程式庫的尋找問題時,我想到了一個程式從生到死的曆程。寫過很多程式,編譯過很多程式,也運行過很多程式,對一個程式的從生到死,感覺很簡單,也就沒有做更多的或者說深入的思考與研究。也許我們習慣了在windows環境下的編程,在那裡我們有很好的IDE,它能把一個工程組織得很好,直接點編譯產生一個可執行檔,然後直接雙擊這個.exe檔案或者建立一個捷徑運行這個程式。以前可能我們也聽說過,源碼先要編譯,然後連結,然後裝載、運行,可我們很少去考慮這背後到底都發生了些什麼,似乎也不用考慮那麼多,因為我們的IDE實在是太智能了,因為我們已經很習慣了使用windows環境,因為在windows下安裝個軟體,我們幾乎只需要點擊下一步就可以了。

        這段時間在做linux系統下opencv2.0到ARM開發板的移植,在這裡,我有很多問題不得不考慮,問題如下:

             程式在編譯時間,源碼所需要的庫(靜態庫和動態庫)及標頭檔編譯器是去哪找的?(庫及標頭檔的尋找)

             當輸入一個命令時,系統時如何找到這個命令的?(命令的尋找)

             程式在運行時,它所需要的庫是去哪找的?(動態連結程式庫的尋找)

       這就是一個程式的由生到死的過程中需要考慮的幾個問題!

       在linux系統下,我們常常要自己通過源碼安裝一些庫,裝一些軟體,第一件事該想到的,編譯產生後的標頭檔,庫或者程式我們該放到哪,是放到/lib  /usr/lib  /usr/include  /usr/bin  /usr/local/lib  /usr/local/include    /usr/local/bin等目錄下嗎?可能有些書會說自己安裝的程式一般放在/usr/local目錄下,放到這下面我可以省心的不用去修改一些環境變數或設定檔了,但接下來我們可能會想,要是我以後想刪除我前面安裝的軟體呢?你還能想起你以前安裝這個軟體時它到底安裝了哪些檔案了嗎?幾乎不可能吧!

         所以當我想安裝一個軟體時我希望像在windows下一樣,把這個軟體安裝在一個單獨的目錄下,比如說我要安裝opencv2.0,那麼我就在/usr/local目錄下建立一個目錄opnecv2.0,然後把所有相關的都安裝到/usr/local/opencv2.0目錄下,這樣如果我以後不想要這個庫時我就可以直接刪掉這個檔案夾就可以了。可在這裡我們就有些問題不得不考慮了:如果我們把opencv安裝到了/usr/local/opencv2.0這個目錄下了,那編譯器在編譯包含有opencv2.0的庫或標頭檔時,編譯器能找著這些標頭檔和庫嗎?如果這是一個可運行檔案,我在其它目錄下運行這個檔案,系統能找到這個檔案嗎?它是如何找到這個檔案的呢?當其它包含有opencv有關函數的程式時,它是如何找到這些庫的呢?由這就引發了我上面提到的三個問題。

         下面我們先來看第一個問題:程式在編譯時間,源碼所需要的庫及標頭檔編譯器是去哪找的?

         在這裡,其實有庫的尋找,和標頭檔的尋找,下面先來講標頭檔的尋找。我們在寫一個比較大型的程式時,總是喜歡把這些函數還有一些資料結構的聲明放在一個檔案中,我們把這種檔案稱為標頭檔,檔案名稱以.h尾碼結尾。在一些源檔案裡,我們可能要包含自己寫的標頭檔,還有一些標準庫的標頭檔比如說stdio.h等等。在編譯的預先處理階段,預先處理程式會將這些標頭檔的內容插到相應的include指令處,現在的問題是編譯器是如何找到這些標頭檔的。

         1. 在編譯時間,我們可以用-I(i的大寫)選項來指定標頭檔所在的目錄,如:

test.h內容如下:

Struct student{    int  age;};

main.c 內容如下:

#include<stdio.h>#include<test.h>int main(){    structstudent st;    st.age= 25;    printf(“st.age=%d\n”,st.age);    return0;}

         可以把test.h放在與main.c同一個目錄下,編譯命令如下:

        xgy@ubuntu:~/tmp/workSpace/testincludedir$  gcc main.c -I./

        如果把test.h放在/usr/include/xgytest目錄下,注意,xgytest是我自己建的一個目錄

       編譯命令如下:xgy@ubuntu:~/tmp/workSpace/testincludedir$  gcc main.c –I/usr/include/xgytest

       注意:在-I後可以有空格也可以沒有空格,另外也可以指定多個目錄,例如,tesh.h放在當前檔案夾下,還有一個teacher.h放在 ./include目錄下,則可以這樣編譯:

      xgy@ubuntu:~/tmp/workSpace/testincludedir$  gcc main.c -I ./ -I ./include/

           2. 設定gcc的環境變數C_INCLUDE_PATH、CPLUS_INCLUDE_PATH 、CPATH。

        C_INCLUDE_PATH編譯 C 程式時使用該環境變數。該環境變數指定一個或多個目錄名列表,尋找標頭檔,就好像在命令列中指定 -isystem 選項一樣。會首先尋找 -isystem 指定的所有目錄。

         CPLUS_INCLUDE_PATH編譯 C++ 程式時使用該環境變數。該環境變數指定一個或多個目錄名列表,尋找標頭檔,就好像在命令列中指定 -isystem 選項一樣。會首先尋找 -isystem 指定的所有目錄。

          CPATH 編譯 C 、 C++ 和 Objective-C 程式時使用該環境變數。該環 境變數指定一個或多個目錄名列表,尋找標頭檔,就好像在命令列中指定-l 選項一樣。會首先尋找-l 指定的所有目錄。

          假設test.h放在/usr/include/xgytest,則對C_INCLUDE_PATH做如下設定:

export C_INCLUDE_PATH=$C_INCLUDE_PATH:/usr/include/xgytest

詳細請況可以參考如下文章:

http://blog.csdn.net/katadoc360/article/details/4151286

 http://blog.csdn.net/dlutxie/article/details/8176164

3. 尋找預設的路徑/usr/include   /usr/local/include等

 

總結一下gcc在編譯源碼時是如何尋找所需要的標頭檔的:

   1.  首先gcc會從-Idir   -isystem dir   -Bprefix    -sysroot  dir     --sysroot=dir    -iquote dir選項指定的路徑尋找(這些選項先指定的會先搜尋,有特例的情況請參考前面的連結)

    2. 然後找gcc的環境變數:C_INCLUDE_PATH、CPLUS_INCLUDE_PATH 、CPATH、GCC_EXEC_PREFIX等。(這些環境變數搜尋的先後順序不確定,有待確認)

   3. 然後尋找GCC安裝的目錄(可以通過gcc  -print-search-dirs查詢)

   4.  然後再按照下面列出的順序尋找系統預設的目錄:/usr/include      /usr/local/include

         

程式在編譯時間,編譯器又是如何尋找所需要的庫的呢?這裡的庫既包括靜態庫又包括動態庫。在這裡,我們得先瞭解兩個概念:庫的連結時路徑和運行時路徑。

        現代連接器在處理動態庫時將連結時路徑(Link-time path)和運行時路徑(Run-time path)分開,使用者可以通過-L指定串連時庫的路徑,通過-R(或-rpath)指定程式執行階段程式庫的路徑,大大提高了庫應用的靈活性。

我們來看幾個例子:

pos.c檔案的內容如下:

#include<stdio.h>void pos(){    printf("the directory is .//n");}

main.c檔案的內容如下:

#include<stdio.h>intmain(){    pos();    return 0;}

接下來看如下執行的命令:

我們來分析下上面圖片中的命令:產生的動態連結程式庫libpos.so放在了當前的路徑下,接著用gcc main.c  –lpos 來連結這個庫卻發現ld找不著這個庫!然後我加了一個-L選項,指出這個庫在當前路徑下,結果編譯通過,可在運行剛編譯產生的a.out時又出現了錯誤!這就是運行是的連結錯誤!運行時的連結問題在後面將有介紹。用ldd命令可以查看一個可執行檔依懶於哪些庫。注意-lpos, 這裡的-l是L的小寫,另外也可以寫成-l  pos即中間有一個空格,但有沒有空格是有一點區別的,有空格的只搜尋與POSIX相容的庫,一般建議使用沒有空格的。

         另外我們可以把剛才編譯產生的libpos.so拷到預設的路徑/lib  /usr/lib /usr/local/lib路徑下,然後直接執行gcc main.c –lpos也可以通過編譯。

         在這裡補充說明一點:Linux下 的庫檔案在命名時有一個約定,那就是庫檔案應該以lib三個字母開頭,由於所有的庫檔案都遵循了同樣的規範,因此在用-l(L的小寫字母)選項指定連結的庫檔案名稱時可以省去 lib三個字母,也就是說GCC在對-lfoo進行處理時,會自動去連結名為libfoo.so的檔案。

每個共用庫也有一個實名,其真正包含有庫的代碼,組成如下:

so+.+子版本號碼+.+發布號(最後的句點和發布號是可選項。)

另外,共用庫還有一個名稱,一般用於編譯串連,稱為連名(linkername),它可以被看作是沒有任何版本號碼的so名。在上面的討論中,我一直是以動態庫(或者說共用庫)為例的,其實對於靜態庫也一樣,只是在這裡又有一個問題,如果在同一個目錄下既有動態庫,又有靜態庫,且它倆的檔案名稱也一樣,只是尾碼不一樣,那連結器在連結時是連結動態庫還是連結靜態庫呢?如果我要指定連結動態庫或者靜態庫又該如何做呢?

           讓我們來看看下面執行的命令(注意下,為了方便,我開了兩個終端):

通過上面的這些命令,也許就能回答我剛提出的兩個問題了。

在這裡,我還想看下編譯時間gcc是否會查LD_LIBRARY_PATH環境變數,還有/etc/ld.so.conf檔案指定的路徑,命令如下:

從上面的命令可以看出,編譯時間,編譯器不會尋找LD_LIBRARY_PATH,還有/etc/ld.so.conf檔案中指定的路徑。下面來總結下:

 

程式在編譯連結時,編譯器是按照如下順序來尋找動態連結程式庫(共用庫)和靜態連結庫的:

1.  gcc會先按照-Ldir    -Bprefix選項指定的路徑尋找

2. 再找gcc的環境變數GCC_EXEC_PREFIX

3. 再找gcc的環境變數LIBRARY_PATH

4. 然後尋找GCC安裝的目錄(可以通過gcc  -print-search-dirs查詢)

5.  然後尋找預設路徑/lib

6.  然後尋找預設路徑/usr/lib

7.  最後尋找預設路徑/usr/local/lib

8.  在同一個目錄下,如果有相同檔案名稱的庫(只是尾碼不同),那麼預設連結的是動態連結程式庫,可以用-static選項顯示的指定連結靜態庫。

 

 第二個問題:當輸入一個命令時,系統時如何找到這個命令的?(命令的尋找)

    如果我們輸入一個命令時帶入路徑時一般是不會不什麼疑問的,因為此時我們執行的就是指定路徑下程式。當我們只輸入一個命令名時會發生什麼情況呢?

當我們鍵入命令名時,linux系統更確切的說應該是shell按照如下順序搜尋:

1.  Shell首先檢查命令是不是保留字(比如for、do等)

2.  如果不是保留字,並且不在引號中,shell接著檢查別名表,如果找到匹配則進行替換,如果別名定義以空格結尾,則對下一個詞作別名替換,接著把替換的結果再跟保留字表比較,如果不是保留字,則shell轉入第3步。

3.  然後,shell在函數表中尋找該命令,如果找到則執行。

4.  接著shell再檢查該命令是不是內部命令(比如cd、pwd)

5.  最後shell在PATH中搜尋以確定命令的位置

6.  如果還是找不到命令則產生“command not found”錯誤資訊。

這裡要注意一點:系統在按PATH變數定義的路徑搜尋檔案時,先搜到的命令先執行。例如,我的PATH變數如下:

root@ubuntu:~# echo $PATH

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:

如果在不同的目錄中有兩個ls檔案,例如/usr/local/sbin/ls, /usr/local/bin/ls,那麼在使用ls的時候,會執行/usr/local/sbin/ls,因為在PATH中哪個目錄先被查詢,則哪個目錄下的檔案就會先執行。

 

第三個問題:程式在運行時,它所需要的庫是去哪找的?(動態連結程式庫的尋找)

        在這裡我沒有提到標頭檔的尋找,因為標頭檔只在編譯的時候才會用到,編譯完後就不需要標頭檔了!另外,這裡的庫指的是動態連結程式庫,靜態連結庫在連結後是不需要了的,因為連結時連結器會把靜態庫中的代碼插入到相應的函數的調用處,所以程式在運行時不再需要靜態庫,而對於動態庫來說,連結時,並沒有將動態庫中的任何代碼或資料拷貝到可執行檔中,而只是拷貝了一些重定位與符號表資訊!所以程式在運行時才需要連結時所使用的動態連結程式庫以執行動態連結程式庫中的代碼!這個可以參考《深入理解電腦系統》第七章。

 

程式運行時動態庫的搜尋路徑搜尋的先後順序是:

1.編譯目標代碼時指定的動態庫搜尋路徑(指的是用-wl,rpath或-R選項而不是-L);

example: gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/lib test.c

2.環境變數LD_LIBRARY_PATH指定的動態庫搜尋路徑;

3.設定檔/etc/ld.so.conf中指定的動態庫搜尋路徑;

4.預設的動態庫搜尋路徑/lib;

5.預設的動態庫搜尋路徑/usr/lib。

在上述1、2、3指定動態庫搜尋路徑時,都可指定多個動態庫搜尋路徑,其搜尋的先後順序是按指定路徑的先後順序搜尋的。

 

上面這個的具體內容可以參考:

http://hi.baidu.com/kkernel/blog/item/ce31bb34a07e6b46251f14cf.html

         在這裡補充說明下:gcc的-Wl,rpath選項可以設定動態庫所在路徑,也就是編譯產生的該程式在運行時將到-Wl,rpath所指定的路徑下去尋找動態庫,如果沒找到則到其它地方去找,並且這個路徑會直接寫在elf檔案(就是產生的可執行檔)中,這樣可以免去設定LD_LIBRARY_PATH。注意,gcc參數設定時-Wl,rpath,/path/to/lib, 中間不能有空格。

gcc -o pos main.c -L. -lpos -Wl,-rpath,./

上面這個命令的意思是:編譯main.c時在目前的目錄下尋找libpos.so這個庫,產生的檔案名稱為pos,當執行pos這個檔案時,在目前的目錄下尋找所需要的動態庫檔案。

可以像下面這個命令一樣指定尋找多個路徑:

gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/libtest.c

更改/etc/ld.so.conf檔案後記得一定要執行命令:ldconfig!該命令會將/etc/ld.so.conf檔案中所有路徑下的庫載入記憶體中。

 

下面對編譯時間庫的尋找與執行階段程式庫的尋找做一個簡單的比較:

1. 編譯時間尋找的是靜態庫或動態庫,而運行時,尋找的只是動態庫。

2. 編譯時間可以用-L指定尋找路徑,或者用環境變數LIBRARY_PATH,而運行時可以用-Wl,rpath或-R選項,或者修改/etc/ld.so.conf檔案或者設定環境變數LD_LIBRARY_PATH.

3. 編譯時間用的連結器是ld,而運行時用的連結器是/lib/ld-linux.so.2.

4. 編譯時間與運行時都會尋找預設路徑:/lib  /usr/lib

5. 編譯時間還有一個預設路徑:/usr/local/lib,而運行時不會預設找查該路徑。

           如果安裝的包或程式沒有放在預設的路徑下,則使用mancommand尋找command的協助時可能查不到,這時可以修改MANPATH環境變數,或者修改/etc/manpath.config檔案。如果使用了pkg-config這個程式來對包進行管理,那麼有可能要設定PKG_CONFIG_PATH環境變數,這個可以參考:http://www.linuxsir.org/bbs/showthread.php?t=184419

 

寫在最後的話

    一個程式的從生到死會發生很多很多的故事,在這裡,我只是從一個角度探討了其中的冰山一角,還有許許多多的問題需要去理解,比如說:編譯連結時,各個檔案是如何連結到一起的?程式運行時,動態庫已經被載入到記憶體中,程式又是如何準確找到動態庫在記憶體中的位置的?動態庫的連結器/lib/ld-linux.so.2自己本身也是一個動態庫,那麼它又是如何被載入記憶體的呢?更深入的想一下,可以認為ld-linux.so.2是隨核心一起載入記憶體的,那核心又是如何載入記憶體的呢?如果說核心是由bootloader載入的,那bootloader又是如何載入記憶體的呢?也許你該想到BIOS了。其中的一些問題可以參考《深入理解電腦系統》這本書。

 

                                                                                                                                                                               整理於2011-9-15     作者:seamus

轉載請註明來源:http://blog.csdn.net/dlutxie/article/details/6776936

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.