Linux下的靜態連結與動態連結

Tattoo_Welkin發表於2017-12-13

什麼是庫

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

本質上來說庫是一種可執行程式碼的二進位制形式,可以被作業系統載入記憶體執行。庫有兩種:靜態庫(.a、.lib)和動態庫(.so、.dll)

所謂靜態、動態是指連結。回顧一下,將一個程式編譯成可執行程式的步驟:
這裡寫圖片描述

這裡寫圖片描述

靜態庫

之所以成為【靜態庫】,是因為在連結階段,會將彙編生成的目標檔案.o與引用到的庫一起連結打包到可執行檔案(.out)中。因此對應的連結方式稱為靜態連結。

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

 1.靜態庫對函式庫的連結是放在編譯時期完成的。
 2.程式在執行時與函式庫再無瓜葛,移植方便。
 3.浪費空間和資源,因為所有相關的目標檔案與牽涉到的函式庫被連結合成一個可執行檔案

下面編寫一些簡單的四則運算C++類,將其編譯成靜態庫給他人用,標頭檔案如下所示:

class StaticMath
{
public:
    StaticMath(void);
    ~StaticMath(void);

    static double add(double a, double b);//加
    static double sub(double a, double b);//減
    static double mul(double a, double b);//乘
    static double div(double a, double b);//除

};

參考學習:C++中的static關鍵字的總結 這個挺重要的,建議看一下!

Linux下使用 ar 工具,將目標檔案壓縮到一起,並且對其進行編號和索引,以便於查詢和檢索。一般建立靜態庫的步驟如圖所示:
這裡寫圖片描述

Linux下建立與使用靜態庫

Linux靜態庫命名規則

Linux靜態庫命名規範,必須*是”lib[your_library_name].a”:lib為字首,中間是靜態庫名,副檔名為 .a

建立靜態庫(.a)
通過上面的流程可以知道,Linux建立靜態庫過程如下:

  • (1)首先,將程式碼檔案編譯成目標檔案.o(StaticMath.o)
//這是StaticMath.cpp 檔案
#include<iostream>
#include"myhead.h"
using namespace std;
double StaticMath::add(double a,double b)
{
    return a+b;
}
double StaticMath::sub(double a,double b)
{
    return a-b;
}
double StaticMath::mul(double a,double b)
{
    return a*b;
}
double StaticMath::div(double a,double b)
{
    return a/b ;
}
    g++ -c StaticMath.cpp

注意帶引數-c,將其編譯為.o 檔案,否則直接編譯為可執行檔案

  • (2)然後,通過 ar 工具將目標檔案打包成 .a 靜態庫檔案

    ar  -jcv  -f  libstaticmath.a     StaticMath.o
    

這裡寫圖片描述

大一點的專案會編寫makefile檔案(CMake等等工程管理工具)來生成靜態庫,輸入多個命令太麻煩了。

  • (3) 使用靜態庫

編寫使用上面建立的靜態庫的測試程式碼:

#include "StaticMath.h"
#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
    double a = 10;
    double b = 2;

    cout << "a + b = " << StaticMath::add(a, b) << endl;
    cout << "a - b = " << StaticMath::sub(a, b) << endl;
    cout << "a * b = " << StaticMath::mul(a, b) << endl;
    cout << "a / b = " << StaticMath::div(a, b) << endl;

    return 0;
}

Linux下使用靜態庫,只需要在編譯的時候,指定靜態庫的搜尋路徑(-L選項)、指定靜態庫名(不需要lib字首和.a字尾,-l選項)。

g++ TestStaticLibrary.cpp -L../StaticLibrary -lstaticmath

-L:表示要連線的庫所在目錄
-l (小寫L):指定連結時需要的動態庫,編譯器查詢動態連線庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.a或.so來確定庫的名稱。

gcc 引數簡介

    -E:只執行到預處理階段,不生成任何檔案
    -S:將C程式碼轉換為彙編程式碼(.s 彙編檔案)
    -c:僅執行編譯操作,不進行連線操作(.o 機器碼)
    -o:指定生成的輸出檔案(.out 可執行檔案) 

    -L:告訴gcc去哪裡找庫檔案。 gcc預設會在程式當前目錄、/lib、/usr/lib和/usr/local/lib下找對應的庫
    -l:用來指定具體的靜態庫、動態庫是哪個
    -I: 告訴gcc去哪裡找標頭檔案

動態庫

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

為什麼還需要動態庫?

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

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

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

動態庫在程式編譯時並不會被連線到目的碼中,而是在程式執行是才被載入。不同的應用程式如果呼叫相同的庫,那麼在記憶體裡只需要有一份該共享庫的例項,規避了空間浪費問題。動態庫在程式執行是才被載入,也解決了靜態庫對程式的更新、部署和釋出頁會帶來麻煩。使用者只需要更新動態庫即可。

動態庫特點總結:

1.動態庫把對一些庫函式的連結載入推遲到程式執行的時期。
2.可以實現程式之間的資源共享。(因此動態庫也稱為共享庫)
3.將一些程式升級變得簡單。甚至可以真正做到連結載入完全由程式設計師在程式程式碼中控制(顯式呼叫)。

Linux下建立與使用動態庫

Linux動態庫的命名規則

動態連結庫的名字形式為 libxxx.so,字首是lib,字尾名為“.so”

建立動態庫(.so)

編寫四則運算動態庫程式碼

// myhead.h 檔案
class DP
{
public:
        static void print_111();
        static void print_222();
        static void print_333();
};

//print_333 檔案(print_111和print_222 檔案與print_333 檔案類似)
#include<iostream>
#include"myhead.h"
using namespace std;
void DP::print_333() 
{
    cout << "333333333333333" <<  endl ;
}

首先,生成目標檔案,此時要加編譯器選項-fpic

g++ -fPIC -c  print_*.cpp

-fPIC 建立與地址無關的編譯程式(pic,position independent code),是為了能夠在多個應用程式間共享。

然後,生成動態庫,此時要加連結器選項 -shared

g++ -shared -o libtest.so  print_*.o

-shared 指定生成動態連結庫。

其實上面兩個步驟可以合併為一個命令:

g++ -fPIC -shared -o libtest.so print_*.cpp

生成libtest.so 動態庫

這裡寫圖片描述

使用動態庫

編寫使用動態庫的測試程式碼:

// testDP.cpp 檔案 
#include "myhead.h"
#include <iostream>
using namespace std;

int main(void)
{
    DP::print_111();
    DP::print_222();
    DP::print_333();
    return 0;
}

引用動態庫編譯成可執行檔案(跟靜態庫方式一樣):

g++ testDP.cpp   -L./   -ltest

然後執行:./a.out,報錯如下:

這裡寫圖片描述

這是由於程式執行時沒有找到動態連結庫造成的。程式編譯時連結動態連結庫和執行時使用動態連結庫的概念是不同的,在執行時,系統能夠知道其所依賴的庫的名字,但是還需要知道絕對路徑。有幾種辦法可以解決此種問題:

1. 因為系統會按照 LD_LIBRARY_PATH 環境變數來查詢除了預設路徑之外( /lib, /usr/lib, /usr/local/lib)的共享庫(動態連結庫)的其他路徑,就像PATH變數一樣!所以我們可以修改該環境變數來解決這個問題。

export LD_LIBRARY_PATH=/home/hp/LinuxC:$LD_LIBRARY_PATH

這裡寫圖片描述

2. 將動態連結庫賦值一份到預設路徑

sudo cp libtest.so /usr/lib

這裡寫圖片描述

3. 因為系統中的配置檔案/etc/ld.so.conf是動態連結庫的搜尋路徑配置檔案,在程式執行時會去讀取該檔案,那麼我們就將我們自己編寫的庫的路徑寫到該配置檔案中去即可。

這裡寫圖片描述

在最後一行加入   /home/liushengxi/C-/自建庫/test
執行ldconfig ,該命令會重建/etc/ld.so.cache檔案

這裡寫圖片描述

idconfig命令查詢:命令查詢

至此,基本連結就是這樣的了~_~

相關文章