GCC建立和使用靜態連結庫(.a檔案)

2020-07-16 10:05:23
Linux 下的靜態連結庫是以.a結尾的二進位制檔案,它作為程式的一個模組,在連結期間被組合到程式中。和靜態連結庫相對的是動態連結庫(.so檔案),它在程式執行階段被載入進記憶體。

製作連結庫的目的是希望別人使用我們已經實現的功能,但又不希望別人看到我們的原始碼,這對商業機構是非常友好的。

Linux 下靜態連結庫檔案的命名規則為:

libxxx.a

xxx 表示庫的名字。例如,libc.a、libm.a、libieee.a、libgcc.a 都是 Linux 系統自帶的靜態庫。

GCC 生成靜態連結庫

1) 首先使用 gcc 命令把原始檔編譯為目標檔案,也即.o檔案:

gcc -c 原始檔列表

-c選項表示只編譯,不連結,我們已在《GCC -c選項》中進行了講解。

2) 然後使用 ar 命令將.o檔案打包成靜態連結庫,具體格式為:

ar rcs + 靜態庫檔案的名字 + 目標檔案列表

ar 是 Linux 的一個備份壓縮命令,它可以將多個檔案打包成一個備份檔案(也叫歸檔檔案),也可以從備份檔案中提取成員檔案。ar 命令最常見的用法是將目標檔案打包為靜態連結庫。

對引數的說明:
  • 引數 r 用來替換庫中已有的目標檔案,或者加入新的目標檔案。
  • 引數 c 表示建立一個庫。不管庫否存在,都將建立。 
  • 引數 s 用來建立目標檔案索引,這在建立較大的庫時能提高速度。

例如,下面的寫法表示將目標檔案 a.o、b.o 和 c.o 打包成一個靜態庫檔案 libdemo.a:

ar rcs libdemo.a a.o b.o c.o

範例演示

在使用者主目錄(home 目錄)下建立一個資料夾 test,將 test 作為整個專案的基礎目錄。在 test 目錄中再建立四個原始檔,分別是 add.c、sub.c、div.c 和 test.h。

add.c 實現兩個數相加,程式碼展示如下:
#include “test.h”
int add(int a,int b)
{
    return a + b;
}
sub.c 實現兩個數相減,程式碼展示如下:
#include “test.h”
int sub(int a,int b)
{
    return a - b;
}
div.c 實現兩個函數相除,程式碼展示如下:
#include “test.h”
int div(int a,int b)
{
    return a / b;
}
還有一個 test.h 標頭檔案,用來宣告三個函數,程式碼展示如下:
#ifndef __TEST_H_
#define __TEST_H_

int add(int a,int b);
int sub(int a,int b);
int div(int a,int b);

#endif

接下來,我們就將以上程式碼製作成靜態連結庫。

首先將所有原始檔都編譯成目標檔案:

gcc -c *.c

*.c表示所有以.c結尾的檔案,也即所有的原始檔。執行完該命令,會發現 test 目錄中多了三個目標檔案,分別是 add.o、sub.o 和 div.o。

然後把所有目標檔案打包成靜態庫檔案:

ar rcs libtest.a *.o

*.o表示所有以.o結尾的檔案,也即所有的目標檔案。執行完該命令,發現 test 目錄中多了一個靜態庫檔案 libtest.a,大功告成。

下面是完整的生成命令:
[c.biancheng.net ~]$ cd test
[c.biancheng.net test]$ gcc -c *.c
[c.biancheng.net test]$ ar rcs libtest.a *.o

GCC 使用靜態連結庫

使用靜態連結庫時,除了需要庫檔案本身,還需要對應的標頭檔案:庫檔案包含了真正的函數程式碼,也即函數定義部分;標頭檔案包含了函數的呼叫方法,也即函數宣告部分。

為了使用上面生成的靜態連結庫 libtest.a,我們需要啟用一個新的專案。在使用者主目錄(home 目錄)中再建立一個資料夾 math,將 math 作為新專案的基礎目錄。

在比較規範的專案目錄中,lib 資料夾一般用來存放庫檔案,include 資料夾一般用來存放標頭檔案,src 資料夾一般用來存放原始檔,bin 資料夾一般用來存放可執行檔案。為了規範,我們將前面生成的 libtest.a 放到 math 目錄下的 lib 資料夾,將 test.h 放到 math 目錄下的 include 資料夾。

在 math 目錄下再建立一個 src 資料夾,在 src 中再建立一個 main.c 原始檔。

此時 math 目錄中檔案結構如下所示:
|-- include
|   `-- test.h
|-- lib
|   `-- libtest.a
`-- src
    `-- main.c

在 main.c 中,可以像下面這樣使用 libtest.a 中的函數:
#include <stdio.h>
#include "test.h"  //必須引入標頭檔案

int main(void)
{
    int m, n;
    printf("Input two numbers: ");
    scanf("%d %d", &m, &n);
    printf("%d+%d=%dn", m, n, add(m, n));
    printf("%d-%d=%dn", m, n, sub(m, n));
    printf("%d÷%d=%dn", m, n, div(m, n));

    return 0;
}
在編譯 main.c 的時候,我們需要使用-I(大寫的字母i)選項指明標頭檔案的包含路徑,使用-L選項指明靜態庫的包含路徑,使用-l(小寫字母L)選項指明靜態庫的名字。所以,main.c 的完整編譯命令為:

gcc src/main.c -I include/ -L lib/ -l test -o math.out

注意,使用-l選項指明靜態庫的名字時,既不需要lib字首,也不需要.a字尾,只能寫 test,GCC 會自動加上字首和字尾。

開啟 math 目錄,發現多了一個 math.out 可執行檔案,使用./math.out命令就可以執行 math.out 進行數學計算。

完整的使用命令如下所示:
[c.biancheng.net ~]$ cd math
[c.biancheng.net math]$ gcc src/main.c -I include/ -L lib/ -l test -o math.out
[c.biancheng.net math]$ ./math.out
Input two numbers: 27 9↙
27+9=36
27-9=18
27÷9=3