1. 程式人生 > >GCC編譯過程與動態鏈接庫和靜態鏈接庫

GCC編譯過程與動態鏈接庫和靜態鏈接庫

elf格式 方式 通過 ifd lan 匯編語言 cpp wid 本質

1. 庫的介紹

庫是寫好的現有的,成熟的,可以復用的代碼。現實中每個程序都要依賴很多基礎的底層庫,不可能每個人的代碼都從零開始,因此庫的存在意義非同尋常

本質上來說庫是一種可執行代碼的二進制形式,可以被操作系統載入內存執行。庫有兩種:靜態庫(.a、.lib)和動態庫(.so、.dll)。 windows上對應的是.lib .dll linux上對應的是.a .so



在這裏先介紹下Linux下的gcc編譯的幾個選項
g++ -c hellospeak.cpp
會將hellospeak.cpp 選項 -c 用來告訴編譯器編譯源代碼但不要執行鏈接,輸出結果為對象文件。文件默認名與源碼文件名相同,只是將其後綴變為 .o。例如,上面的命令將編譯源碼文件hellospeak.cpp 並生成對象文件 hellospeak.o;

下面這條命令將上述兩個源碼文件編譯鏈接成一個單一的可執行程序:
$ g++ hellospeak.cpp speak.cpp -o hellospeak
如果沒有-o和後面的參數,編譯器采用默認的 a.out
本例中就會生成hellospeak 這樣的可執行程序。

所謂靜態、動態是指鏈接。回顧一下,將一個程序編譯成可執行程序的步驟:

圖:編譯過程

技術分享圖片 靜態庫

之所以成為【靜態庫】,是因為在鏈接階段,會將匯編生成的目標文件.o與引用到的庫一起鏈接打包到可執行文件中。因此對應的鏈接方式稱為靜態鏈接。


試想一下,靜態庫與匯編生成的目標文件一起鏈接為可執行文件,那麽靜態庫必定跟.o文件格式相似。其實一個靜態庫可以簡單看成是一組目標文件(.o/.obj文件)的集合,即很多目標文件經過壓縮打包後形成的一個文件。靜態庫特點總結:


l 靜態庫對函數庫的鏈接是放在編譯時期完成的。

l 程序在運行時與函數庫再無瓜葛,移植方便。

l 浪費空間和資源,因為所有相關的目標文件與牽涉到的函數庫被鏈接合成一個可執行文件。

Linux下創建與使用靜態庫

Linux靜態庫命名規則

Linux靜態庫命名規範,必須是"lib[your_library_name].a":lib為前綴,中間是靜態庫名,擴展名為.a。

創建靜態庫(.a)

通過上面的流程可以知道,Linux創建靜態庫過程如下:

l 首先,將代碼文件編譯成目標文件.o(StaticMath.o)

g++ -c StaticMath.cpp

註意帶參數-c,否則直接編譯為可執行文件

然後,通過ar工具將目標文件打包成.a靜態庫文件

ar -crv libstaticmath.a StaticMath.o

生成靜態庫libstaticmath.a

技術分享圖片

動態庫

通過上面的介紹發現靜態庫,容易使用和理解,也達到了代碼復用的目的,那為什麽還需要動態庫呢?

為什麽還需要動態庫?

為什麽需要動態庫,其實也是靜態庫的特點導致。

l 空間浪費是靜態庫的一個問題。

技術分享圖片

另一個問題是靜態庫對程序的更新、部署和發布頁會帶來麻煩。如果靜態庫liba.lib更新了,所以使用它的應用程序都需要重新編譯、發布給用戶(對於玩家來說,可能是一個很小的改動,卻導致整個程序重新下載,全量更新)。


動態庫在程序編譯時並不會被連接到目標代碼中,而是在程序運行是才被載入。不同的應用程序如果調用相同的庫,那麽在內存裏只需要有一份該共享庫的實例,規避了空間浪費問題。動態庫在程序運行是才被載入,也解決了靜態庫對程序的更新、部署和發布頁會帶來麻煩。用戶只需要更新動態庫即可,增量更新

技術分享圖片

動態庫特點總結:

l 動態庫把對一些庫函數的鏈接載入推遲到程序運行的時期。

l 可以實現進程之間的資源共享。(因此動態庫也稱為共享庫)

l 將一些程序升級變得簡單。

l 甚至可以真正做到鏈接載入完全由程序員在程序代碼中控制(顯示調用)。

Window與Linux執行文件格式不同,在創建動態庫的時候有一些差異。

l 在Windows系統下的執行文件格式是PE格式,動態庫需要一個DllMain函數做出初始化的入口,通常在導出函數的聲明時需要有_declspec(dllexport)關鍵字

l Linux下gcc編譯的執行文件默認是ELF格式,不需要初始化入口,亦不需要函數做特別的聲明,編寫比較方便。

與創建靜態庫不同的是,不需要打包工具(ar、lib.exe),直接使用編譯器即可創建動態庫。


參考於:

http://www.cnblogs.com/skynet/p/3372855.html   吳秦  (很詳細!值得細看)  
http://www.cnblogs.com/iloveyoucc/archive/2012/08/29/2661851.html
參考來源:知乎 郭無心



2. GCC編譯過程 2.1 GCC定義 目前 Linux 下最常用的 C 語言編譯器是 GCC ( GNU Compiler Collection ),它是 GNU 項目中符合 ANSI C 標準的編譯系統,能夠編譯用 C 、 C++ 和 Object C 等語言編寫的程序。 GCC 不僅功能非常強大,結構也異常靈活。最值得稱道的一點就是它可以通過不同的前端模塊來支持各種語言,如Java 、 Fortran 、 Pascal 、 Modula-3 和 Ada 等。開放、自由和靈活是 Linux 的魅力所在,而這一點在 GCC 上的體現就是程序員通過它能夠更好地控制整個編譯過程。在使用 GCC 編譯程序時,編譯過程可以被細分為四個階段: 預處理( Pre-Processing )
編譯( Compiling )
匯編( Asse mbling )
鏈接( Linking )
Linux 程序員可以根據自己的需要讓 GCC 在編譯的任何階段結束,以便檢查或使用編譯器在該階段的輸出信息,或者對最後生成的二進制文件進行控制,以便通過加入不同數量和種類的調試代碼來為今後的調試做好準備。和其它常用的編譯器一樣, GCC 也提供了靈活而強大的代碼優化功能,利用它可以生成執行效率更高的代碼。
GCC 提供了 30 多條警告信息和三個警告級別,使用它們有助於增強程序的穩定性和可移植性。此外, GCC 還對標準的 C 和 C++ 語言進行了大量的擴展,提高程序的執行效率,有助於編譯器進行代碼優化,能夠減輕編程的工作量。 技術分享圖片 技術分享圖片

2.2 GCC編譯過程

1)gcc 預處理階段:主要對包含的頭文件(#include )和宏定義(#define,#ifdef … )進行處理。可以使用“gcc -E” 讓gcc 在預處理之後停止編譯過程,生成 *.i 文件。

 gcc -E hello.c -o hello.i

2)gcc 編譯階段:gcc 首先要檢查代碼的規範性,是否有語法錯誤等。以確定代碼實際要做的工作,在檢查無誤後,gcc 把代碼翻譯成匯編語言。用戶可以使用-S 選項進行查看,該選項只進
行編譯而不進行匯編,生成匯編代碼。

gcc -S hello.i -o hello.s

3)gcc 匯編階段:生成目標代碼 *.o ;有兩種方式:使用 gcc 直接從源代碼生成目標代碼 gcc -c *.s -o *.o 以及使用匯編器從匯編代碼生成目標代碼 as *.s -o *.o

gcc -c hello.s -o hello.o
as hello.s -o hello.o

也可以直接使用as *.s, 將執行匯編、鏈接過程生成可執行文件a.out, 可以像上面使用-o 選項指定輸出文件的格式。
4)gcc 鏈接階段:生成可執行文件;可以生成的可執行文件格式有: a.out/*/,當然可能還有其它格式。

gcc hello.o     生成可執行文件 a.out
gcc hello.o -o hello        生成可執行文件 hello

2.3 gcc 常用編譯選項:

技術分享圖片

2.3 gcc 鏈接庫文件的使用

在 linux 下開發軟件時,完全不使用第三方函數庫的情況是比較少見的,通常來講都需要借助一個或多個函數庫的支持才能夠完成相應的功能。從程序員的角度看,函數庫實際上就是一些頭文件( .h )和庫文件( .so 或者 .a )的集合。雖然 Linux 下的大多數函數都默認將頭文件放到/usr/include/ 目錄下,而庫文件則放到 /usr/lib/ 目錄下,但並不是所有的情況都是這樣。正因如此, GCC 在編譯時必須有自己的辦法來查找所需要的頭文件和庫文件。 GCC 采用搜索目錄的辦法來查找所需要的文件, -I 選項可以向 GCC 的頭文件搜索路徑中添加新的目錄。例如,如果在/home/justin/include/ 目錄下有編譯時所需要的頭文件,為了讓 GCC 能夠順利地找到它們,就可以使用 -I 選項:

gcc foo.c -I /home/justin/include -o foo 

同樣,如果使用了不在標準位置的庫文件,那麽可以通過 -L 選項向 GCC 的庫文件搜索路徑中添加新的目錄。例如,如果在 /home/xiaowp/lib/ 目錄下有鏈接時所需要的庫文件 libfoo.so ,為了讓 GCC 能夠順利地找到它,可以使用下面的命令:

gcc foo.c -L /home/justin/lib -lfoo -o foo

值得好好解釋一下的是 -l 選項,它指示 GCC 去連接庫文件 libfoo.so 。 Linux 下的庫文件在命名時有一個約定,那就是應該以lib 三個字母開頭,由於所有的庫文件都遵循了同樣的規範,因此在用-l 選項指定鏈接的庫文件名時可以省去lib 三個字母,也就是說GCC 在對-lfoo 進行處理時,會自動去鏈接名為libfoo.so 。
Linux 下的庫文件分為兩大類分別是動態鏈接庫(通常以.so 結尾)和靜態鏈接庫(通常以.a 結尾),兩者的差別僅在程序執行時所需的代碼是在運行時動態加載的,還是在編譯時靜態加載的 。默認情況下,GCC 在鏈接時優先使用動態鏈接庫,只有當動態鏈接庫不存在時才考慮使用靜態鏈接庫,如果需要的話可以在編譯時加上-static 選項,強制使用靜態鏈接庫。例如,如果在home/justin/lib/ 目錄下有鏈接時所需要的庫文件libfoo.so 和libfoo.a ,為了讓GCC 在鏈接時只用到靜態鏈接庫,可以使用下面的命令:

gcc foo.c -L /home/justin/lib -static -lfoo -o foo

對於動態庫和靜態庫文件的創建方法,見上文庫的介紹。

GCC編譯過程與動態鏈接庫和靜態鏈接庫