gcc/g++ 鏈接庫的編譯與鏈接
程序編譯一般需要經預處理、編譯、彙編和鏈接幾個步驟。在實際應用中,有些公共代碼需要反覆使用,就把這些代碼編譯成為「庫」文件。在鏈接步驟中,連接器將從庫文件取得所需的代碼,複製到生成的可執行文件中,這種庫稱為靜態(鏈接)庫,其特點是可執行文件中包含了庫代碼的一份完整拷貝,缺點是被多次使用就會多份冗餘拷貝。還有一種庫,就是程序在開始運行後調用庫函數時才被載入,這種庫獨立於現有的程序,其本身不可執行,但包含著程序需要調用的一些函數,這種庫稱為動態(鏈接)庫(Dynamic Link Library)。
在widows平台下,靜態鏈接庫是.lib文件,動態庫文件是.dll文件。在linux平台下,靜態鏈接庫是.a文件,動態鏈接庫是.so文件。這裡主要講在linux平台下的動態庫和靜態庫的生成以及鏈接。本文主要參考【1】【2】【3】【4】
一、庫的基本知識
首先說明要對庫有一個比較直觀的理解。庫是寫好的現有的,成熟的,可以復用的代碼。現實中每個程序都依賴很多基礎的底層庫,不可能每個人的代碼都從零開始,因此庫的存在意義非同尋常。本質上說來庫是一種可執行代碼的二進位形式(注,其本身不可執行),可以被操作系統載入內存執行。
靜態鏈接庫,之所以稱為「靜態庫」,是因為在鏈接階段,會將彙編生成的目標文件.o與引用到的庫一起鏈接打包到可執行文件中,因此對應的鏈接方式為靜態鏈接。其實一個靜態鏈接庫可以簡單看成一組目標文件(.o/.obj文件)的集合,即很多目標文件經過壓縮打包後形成的一個文件。靜態庫特點總結:
1. 靜態庫對函數庫的鏈接是放在編譯時期完成
2. 程序在運行時對函數庫再唔瓜葛,一直方便。
3. 浪費空間和資源,因為所有相關的目標文件和牽涉到的函數庫被鏈接合成一個可執行文件。
linux下使用ar工具(windows下用lib.exe)(具體的用法參考【5】),可以將目標文件壓縮到一起,並且對其進行編號和索引,一便於查找和索引。一般創建靜態鏈接庫的步驟如下:
靜態鏈接庫的命名規則,庫的名稱和庫文件名稱不同,有聯繫,假定庫名稱為"my_library_name",那麼起庫文件名為"lib[my_library_name].a"(方括弧是為了區分,實際上沒有)
動態鏈接庫,在程序編譯是並不會被連接到目標代碼中,而是在程序運行時才被載入。不同的應用程序如果調用相同的庫,那麼在內存里只需要有一份該共享庫的實例,規避了空間浪費問題。動態庫的一些總結:
1. 動態庫把對一些庫函數的鏈接載入推遲到程序運行時期
2. 可以實現進程之間的資源共享,(動態庫也成為共享庫)
3. 將一些程序升級變得簡單
4. 設置可以真正做到鏈接載入完全由程序員在程序代碼中控制(顯式調用)
Linux下gcc編譯的執行文件默認是ELF格式,不需要初始化入口,亦不需要函數做特別的聲明,編寫比較方便。與windows系統下的格式不同。與創建靜態庫不同的是,不需要打包工具,直接使用編譯器即可創建動態庫。
動態鏈接庫的命名規則,與靜態鏈接庫的方式相同,不過其後綴名為.so,命名形式為"lib[my_library_name].so" 。但是在實際使用過程中libxxx.so 大多數情況只是一個鏈接,它鏈接到一個包含版本信息的庫文件 libxxxx.so.xx,如下圖。當然自己可以使用 ln 命令,製作鏈接 ln -s libxxxx.so.xx libxxxx.so。
二、庫的編譯和鏈接
下面使用一個例子來說明鏈接庫是如何生成與鏈接的。這個例子的源代碼參考【4】。這裡有五個文件,頭文件「SoDemoTest.h」,三個cpp文件「one.cpp」、"two.cpp"、"three.cpp",main函數實現文件「main.cpp」。
[cpp] view plain copy
- #ifndef _SO_DEMO_TEST_HEADER_
- #define _SO_DEMO_TEST_HEADER_
- #include <iostream>
using
namespace
std;void
one();void
two();void
three();- #endif
[cpp] view plain copy
- /* one.cpp */
- #include "SoDemoTest.h"
void
one(){- cout << "call one() function" << endl;
- }
[plain] view plain copy
- /* two.cpp */
- #include "SoDemoTest.h"
- void two(){
- cout << "call two() function" << endl;
- }
[cpp] view plain copy
- /* three.cpp */
- #include "SoDemoTest.h"
void
three(){- cout << "call three() function" << endl;
- }
[cpp] view plain copy
- /* main.cpp */
- #include "SoDemoTest.h"
int
main(){- one();
- two();
- three();
return
0;- }
gcc/g++的編譯參數,這裡只介紹 -L 、-l、-include、-I、-shared、-fPIC
-L :表示要鏈接的庫所在的目錄。-L. 表示要鏈接的庫在當前目錄, -L/usr/lib 表示要連接的庫在/usr/lib下。目錄在/usr/lib時,系統會自動搜索這個目錄,可以不用指明。
-l (L的小寫):表示需要鏈接庫的名稱,注意不是庫文件名稱,比如庫文件為 libtest.so,那麼庫名稱為test
-include :包含頭文件,這個很少用,因為一般情況下在源碼中,都有指定頭文件。
-I (i 的大寫):指定頭文件的所在的目錄,可以使用相對路徑。
-shared :指定生成動態鏈接庫
-fPIC: 表示編譯為位置獨立的代碼,不用此選項的話編譯後的代碼是位置相關的所以動態載入時事通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼共享的目的。
生成鏈接庫
第1步,生成目標文件:g++ -c xxx.cpp
第2步,創建靜態鏈接庫: ar cqs libxxxx.a xx1.o xx2.o xx3.o (參數選項請看【5】)
第3步,程序中使用靜態鏈接庫
第4步,創建動態鏈接庫 g++ -fPIC -shared -o libxxx.so xx1.cpp xx2.cpp xx3.cpp
第5步,動態鏈接庫使用
庫的鏈接,上面簡單演示了一遍庫的生成過程,但是還有很多細節沒有講清楚。以下問題需要注意:
1. 鏈接過程中可能出現多種鏈接方式,需要使用一些參數來指定,下面只是一個演示,在測試時,自己填寫具體的名稱
[plain] view plain copy
- g++ testmain.o -o testmain -WI,-Bstatic -lstaticlib -WI,-Bdynamic -ldynamiclib
2. 鏈接過程中同一個庫(名稱相同)的靜態和動態兩種鏈接庫,在鏈接過程中,系統優先選擇動態鏈接庫
3. 動態鏈接庫路徑,系統默認在/usr/lib 和/usr/local/lib兩個庫目錄搜索,自己定義的庫需要格外指定路徑(設定變數LD_LIABRARY_PATH)或者將其拷貝到這兩個目錄下,在上面的例子的測試過程,已經有說明。當然也可以將當前路徑添加到/etc/ld.so.conf文件中或者/etc/ld.so.conf.d目錄下的一個文件中。
4. 查看動態鏈接庫。有時候可能需要查看一個庫中到底有哪些函數,nm命令可以列印出庫中的涉及到的所有符號。庫既可以是靜態的也可以是動態的。nm列出的符號有很多,常見的有三種:
一種是在庫中被調用,但並沒有在庫中定義(表明需要其他庫支持),用U表示;
一種是在庫中定義的函數,用T表示,這是最常見的;
另一種所謂的「弱態」符號,它們雖然在庫中定義,但可能被其他庫中的同名符號覆蓋,用W表示。
使用ldd命令可以查看程序的庫依賴:
註:更多詳細的信息請閱讀下面的鏈接中的內容~
※同步阻塞 非同步阻塞 同步非阻塞 非同步非阻塞
※Go image網站開發
TAG:程序員小新人學習 |