1. 程式人生 > >gcc/g++ 連結庫的編譯與連結

gcc/g++ 連結庫的編譯與連結

 gcc/g++ 連結庫的編譯與連結

[email protected]

http://blog.csdn.net/surgewong

      程式編譯一般需要經預處理、編譯、彙編和連結幾個步驟。在實際應用中,有些公共程式碼需要反覆使用,就把這些程式碼編譯成為“庫”檔案。在連結步驟中,聯結器將從庫檔案取得所需的程式碼,複製到生成的可執行檔案中,這種庫稱為靜態(連結)庫,其特點是可執行檔案中包含了庫程式碼的一份完整拷貝,缺點是被多次使用就會多份冗餘拷貝。還有一種庫,就是程式在開始執行後呼叫庫函式時才被載入,這種庫獨立於現有的程式,其本身不可執行,但包含著程式需要呼叫的一些函式,這種庫稱為動態(連結)庫(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”。

#ifndef _SO_DEMO_TEST_HEADER_
#define _SO_DEMO_TEST_HEADER_
#include <iostream>
using namespace std;
void one();
void two();
void three();
#endif
/* one.cpp */
#include "SoDemoTest.h"
void one(){
    cout << "call one() function" << endl;
}
/* two.cpp */
#include "SoDemoTest.h"
void two(){
    cout << "call two() function" << endl;
}
/* three.cpp */
#include "SoDemoTest.h"
void three(){
    cout << "call three() function" << endl;
}

/* 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. 連結過程中可能出現多種連結方式,需要使用一些引數來指定,下面只是一個演示,在測試時,自己填寫具體的名稱

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命令可以檢視程式的庫依賴:



         注:更多詳細的資訊請閱讀下面的連結中的內容~

       


【1】部落格園:C++靜態庫與動態庫

【2】CSDN:Linux 靜態庫&動態庫呼叫

【3】部落格園:gcc/g++ 動態編譯和連結問題

【4】Linux公社:用g++編譯生成動態連線庫*.so的方法及連線

【5】CSDN:linux ar命令