溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

發(fā)布時間:2022-11-01 09:20:12 來源:億速云 閱讀:140 作者:iii 欄目:開發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“GCC指令及動態(tài)庫、靜態(tài)庫怎么使用”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“GCC指令及動態(tài)庫、靜態(tài)庫怎么使用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。

    一、GCC

    1.1 GCC 介紹

    GCC 是 Linux 下的編譯工具集,是「GNU Compiler Collection」的縮寫,包含 gcc、g++ 等編譯器。這個工具集不僅包含編譯器,還包含其他工具集,例如 ar、nm 等。

    GCC 工具集不僅能編譯 C/C++ 語言,其他例如 Objective-C、Pascal、Fortran、Java、Ada 等語言均能進(jìn)行編譯。GCC 還可以根據(jù)不同的硬件平臺進(jìn)行編譯,即能進(jìn)行交叉編譯,在 A 平臺上編譯 B 平臺的程序,支持常見的 X86、ARM、PowerPC、mips 等,以及 Linux、Windows 等軟件平臺。

    1.2 安裝 GCC

    首先,查看 gcc 是否安裝:

    # 查看 gcc 版本
    $ gcc -v
    $ gcc --version
    
    # 查看 g++ 版本
    $ g++ -v
    $ g++ --version

    如果在輸入指令后可以獲取到 gcc 版本,那么就表明你的 Linux 中已經(jīng)安裝了 gcc:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    如果沒有安裝,則可按照如下方法安裝 gcc:

    # centos
    $ sudo yum update   		# 更新本地的軟件下載列表, 得到最新的下載地址
    $ sudo yum install gcc g++	# 通過下載列表中提供的地址下載安裝包, 并安裝

    1.3 GCC 工作流程

    1.3.1 一般使用流程

    首先準(zhǔn)備一個 C 語言代碼,并命名為 test.c:

    #include <stdio.h>
    #define MAX 3
    int main()
    {
        int i;
        for (i = 1; i <= MAX; i++) 
        {
            printf("Hello World\n"); // 輸出 Hello World
        }
    
        return 0;
    }

    一般情況下,我們可以直接通過 $ gcc test.c -o test編譯 test.c,并通過$ ./test指令運(yùn)行生成的可執(zhí)行文件:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    -o:output,是 gcc 編譯器的可選參數(shù),用于指定輸出文件名及路徑,默認(rèn)輸出到當(dāng)前路徑下。下圖展示了如何通過 -o 參數(shù)修改輸出路徑:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    或者不使用 -o 參數(shù),則生成一個默認(rèn)名稱的可執(zhí)行文件 a.out:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    實(shí)際上,GCC 編譯器在對程序進(jìn)行編譯的時候,分為了四個步驟:

    • 預(yù)處理(Pre-Processing):

      • 在這個階段主要做了三件事:展開頭文件 、宏替換 、去掉注釋行

      • 結(jié)果得到的還是一個 C 程序,通常是以 .i 作為文件擴(kuò)展名

    • 編譯(Compiling) :

      • 在這個階段中,gcc 首先要檢查代碼的規(guī)范性、是否有語法錯誤等,以確定代碼實(shí)際要做的工作

      • 在檢查無誤后,gcc 把代碼編譯成匯編代碼,得到一個以 .s 作為文件拓展名的匯編文件。

    匯編(Assembling):

    + 匯編階段是把編譯階段生成的 .s 文件轉(zhuǎn)化成目標(biāo)文件
    + 最終得到一個以 .o 結(jié)尾的二進(jìn)制文件
    • 鏈接(Linking):這個階段需要 GCC 調(diào)用鏈接器對程序需要調(diào)用的進(jìn)行鏈接,最終得到一個可執(zhí)行的二進(jìn)制文件

    而 GCC 的編譯器可以將這 4 個步驟合并成一個,這也就是為什么我們使用$ gcc test.c -o test就可以直接生成可執(zhí)行文件 test 的原因。下面我們對這 4 個步驟做個詳細(xì)的介紹。

    1.3.2 詳細(xì)的工作流程
    1.3.2.1 預(yù)處理
    # 通過添加參數(shù) -E 生成預(yù)處理后的 C 文件 test.i
    # 必須通過 -o 參數(shù)指定輸出的文件名
    $ gcc -E test.c -o test.i

    讓我們來觀察一下 test.i 中的代碼內(nèi)容(太長了,只觀察 main 函數(shù)中的替換情況):

    int main()
    {
        int i;
        for (i = 1; i <= 3; i++)
        {
            printf("Hello World\n");
        }
    
        return 0;
    }

    通過分析 test.i 可以發(fā)現(xiàn):

    • 宏定義 MAX 被替換為了相應(yīng)的值 3

    • 注釋「// 輸出 Hello World」也被去掉了

    1.3.2.2 編譯
    # 通過添加參數(shù) -S 將 test.i 轉(zhuǎn)換為匯編文件 test.s(默認(rèn)生成 .s 文件)
    $ gcc -S test.i
    $ gcc -S test.i -o test.s # 寫法二
    1.3.2.3 匯編
    # 通過匯編得到二進(jìn)制文件 test.o(默認(rèn)生成 .o 文件,object)
    $ gcc -c test.s
    $ gcc -c test.s -o test.o # 寫法二
    1.3.2.4 鏈接
    # 通過鏈接得到可執(zhí)行文件 test
    $ gcc test.o -o test

    在成功生成 test.o 文件后,就進(jìn)入了鏈接階段。在這里涉及到一個重要的概念:函數(shù)庫。

    在 test.c 的代碼中,我們通過print()函數(shù)打印 Hello World 語句;但是在這段程序中并沒有定義 printf 的函數(shù)實(shí)現(xiàn),且在預(yù)編譯中包含進(jìn)去的「stdio.h」中也只有該函數(shù)的聲明extern int printf (const char *__restrict __format, ...);,而沒有定義函數(shù)的實(shí)現(xiàn),那么是在哪里實(shí)現(xiàn)的呢?

    答案就是:系統(tǒng)把這些函數(shù)實(shí)現(xiàn)都做到了名為 libc.so.6 的庫文件中去了,在沒有特別指定時,gcc 會到系統(tǒng)默認(rèn)的搜索路徑 /usr/lib64 下進(jìn)行查找,也就是鏈接到 libc.so.6 庫函數(shù)中去,這樣就有函數(shù) printf 的實(shí)現(xiàn)了,而這也就是鏈接的作用。

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    而函數(shù)庫一般分為靜態(tài)庫和動態(tài)庫兩種:

    • 靜態(tài)庫是指在編譯鏈接時,把庫文件的代碼全部加入到可執(zhí)行文件中,因此生成的文件比較大,但在運(yùn)行時也就不需要庫文件了。在 Linux 中靜態(tài)庫一般以 .a 作為后綴。

    • 動態(tài)庫與之相反,在編譯鏈接時并沒有把庫文件的代碼加入到可執(zhí)行文件中,而是在程序執(zhí)行時鏈接文件加載庫,這樣就可以節(jié)省系統(tǒng)的開銷。在 Linux 中動態(tài)庫一般以 .so 作為后綴。

    如前面所述的 libc.so.6 就是動態(tài)庫,gcc 在編譯時默認(rèn)使用動態(tài)庫。完成了鏈接之后,gcc 就可以生成可執(zhí)行文件了。

    1.3.2.5 總結(jié)

    最后,通過一張圖來總結(jié)一下上述流程:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    在 Linux 下使用 GCC 編譯器編譯單個文件十分簡單,直接使用$ gcc test.c(test.c 為要編譯的 C 語言的源文件),GCC 會自動生成文件名為 a.out 的可執(zhí)行文件(也可以通過參數(shù) -o 指定生成的文件名);也就是通過一個簡單的命令就可以將上邊提到的 4 個步驟全部執(zhí)行完畢了;但是如果想要單步執(zhí)行也是沒問題的。

    1.4 GCC 常用參數(shù)

    下面的表格中列出了一些常用的 gcc 參數(shù),這些參數(shù)在 gcc 命令中沒有位置要求,只需要編譯程序的時候?qū)⑿枰膮?shù)指定出來即可。

    gcc 編譯選項(xiàng)解釋說明
    -E預(yù)處理,主要是進(jìn)行宏展開等步驟,生成 test.i
    -S編譯指定的源文件,但是不進(jìn)行匯編,生成 test.s
    -c編譯、匯編源文件,但是不進(jìn)行鏈接,生成 test.o
    -o指定鏈接的文件名及路徑
    -g在編譯的時候,生成調(diào)試信息,該程序可以被調(diào)試器調(diào)試
    -D在程序編譯的時候,指定一個宏
    -std指定 C 方言,如 -std=c99。gcc 默認(rèn)的方言是 GNU C
    -l在程序編譯的時候,指定使用的庫(庫的名字一定要掐頭去尾,如 libtest.so 變?yōu)?test)
    -L在程序編譯的時候,指定使用的庫的路徑
    -fpic生成與位置無關(guān)的代碼
    -shared生成共享目標(biāo)文件,通常用在建立動態(tài)庫時
    1.4.1 指定一個宏(-D)

    在程序中我們可以通過使用#define定義一個宏,也可以通過宏控制某段代碼是否能夠被執(zhí)行。

    #include <stdio.h>
    
    int main()
    {
        int num = 60;
        printf("num = %d\n", num);
    #ifdef DEBUG
        printf("定義了 DEBUG 宏, num++\n");
        num++;
    #else
        printf("未定義 DEBUG 宏, num--\n");
        num--;
    #endif
    
        printf("num = %d\n", num);
    
        return 0;
    }

    由于我們在程序中并沒有定義 DEBUG 宏,所以第 8~9 行的代碼就不會被執(zhí)行:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    那么如何才能夠在程序中不定義 DEBUG 宏的情況下執(zhí)行第 8~9 行的代碼呢?答案是通過 -D 參數(shù):

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    需要注意的是,-D 參數(shù)必須在生成 test.o 前使用(鏈接前)。如下所示,是無效的:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    說了這么多,-D 參數(shù)有什么用呢?下面我們簡單敘述一下 -D 參數(shù)的應(yīng)用場景。

    1.4.1.1 應(yīng)用場景一

    在發(fā)布程序的時候,一般都會要求將程序中所有的 log 輸出去掉,如果不去掉會影響程序的執(zhí)行效率,很顯然刪除這些打印 log 的源代碼是一件很麻煩的事情,解決方案是這樣的:

    • 將所有的打印 log 的代碼都寫到一個宏判定中,可以模仿上邊的例子;

    • 在調(diào)試程序的時候指定 -D,就會有 log 輸出;

    • 在發(fā)布程序的時候不指定 -D,log 就不會輸出;

    1.4.1.2 應(yīng)用場景二

    或者,你編寫的一個軟件,某個付費(fèi)功能只對已付費(fèi)的用戶 A 開放,但不對白嫖的用戶 B 開放,其中一種解決方法是:

    • 每個用戶對應(yīng)一個維護(hù)分支,用戶 A 對應(yīng) project_1 分支包含付費(fèi)功能的代碼,用戶 B 對應(yīng)的 project_2 分支不包含付費(fèi)功能的代碼。

    • 當(dāng)用戶 B 付費(fèi)訂閱時,再將付費(fèi)項(xiàng)目的代碼拷貝到 project_2 中

    如果再來一個用戶 C 呢?有沒有感覺很麻煩的樣子?那么我們完全可以這樣做:

    #include <stdio.h>
    
    int main()
    {
    #ifdef CHARGE
        //付費(fèi)用戶執(zhí)行流程
        printf("該用戶已付費(fèi),執(zhí)行付費(fèi)功\n");
    #else
        //白嫖用戶執(zhí)行流程
        printf("白嫖用戶,拒絕執(zhí)行付費(fèi)功能\n");
    #endif
    
        printf("公共功能\n");
    
        return 0;
    }

    在編譯付費(fèi)用戶的時候,添加 -D CHARGE 參數(shù);編譯白嫖用戶,則不添加。這樣的話,不管來多少用戶,都只需要維護(hù)一個分支即可。

    1.4.2 指定 C 方言(-std)

    對于如下 C 語言代碼:

    #include <stdio.h>
    
    int main()
    {
        for (int i = 1; i <= 3; i++)
        {
            printf("i = %d\n", i);
        }
    
        return 0;
    }

    在編譯時是會報錯的:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    但如果我們加上 -std=c99,就可以了:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    二、靜態(tài)庫和動態(tài)庫

    2.1 掃盲

    庫是「已經(jīng)寫好的、供使用的」可復(fù)用代碼,每個程序都要依賴很多基礎(chǔ)的底層庫。

    從本質(zhì)上,庫是一種可執(zhí)行代碼的二進(jìn)制形式,可以被操作系統(tǒng)載入內(nèi)存執(zhí)行。程序中調(diào)用的庫有兩種「靜態(tài)庫和動態(tài)庫」,所謂的「靜態(tài)、動態(tài)」指的是鏈接的過程。

    2.2 靜態(tài)庫

    2.2.1 靜態(tài)庫簡介

    在 Linux 中靜態(tài)庫以 lib 作為前綴、以 .a 作為后綴,形如 libxxx.a(其中的 xxx 是庫的名字,自己指定即可)。靜態(tài)庫以之所以稱之為「靜態(tài)庫」,是因?yàn)樵阪溄与A段,會將匯編生成的目標(biāo)文件 .o 與引用的庫一起鏈接到可執(zhí)行文件中,對應(yīng)的鏈接方式稱為靜態(tài)鏈接。

    2.2.2 靜態(tài)庫的生成

    在 Linux 中靜態(tài)庫由程序 ar 生成。生成靜態(tài)庫,需要先對源文件進(jìn)行匯編操作得到二進(jìn)制格式的目標(biāo)文件(以 .o 結(jié)尾的文件),然后再通過 ar 工具將目標(biāo)文件打包就可以得到靜態(tài)庫文件了。

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    使用 ar 工具創(chuàng)建靜態(tài)庫的一般格式為$ ar -rcs libxxx.a 若干原材料(.o文件)

    2.2.3 靜態(tài)庫的制作舉例

    在某目錄中有如下源文件,用來實(shí)現(xiàn)一個簡單的計算器。

    add.c

    #include <stdio.h>
    
    int add(int a, int b)
    {
        return a + b;
    }

    sub.c

    #include <stdio.h>
    
    int subtract(int a, int b)
    {
        return a - b;
    }

    mult.c

    #include <stdio.h>
    
    int multiply(int a, int b)
    {
        return a * b;
    }

    具體操作步驟如下:

    # 第一步:將源文件 add.c、sub.c、mult.c 進(jìn)行匯編,得到二進(jìn)制目標(biāo)文件 add.o、sub.o、mult.o
    $ gcc -c add.c sub.c mult.c
    
    # 第二步:將生成的目標(biāo)文件通過 ar 工具打包生成靜態(tài)庫
    $ ar rcs libcalc.a add.o sub.o mult.o
    2.2.4 靜態(tài)庫的使用

    定義 main 函數(shù)如下所示:

    main.c

    #include <stdio.h>
    
    int main()
    {
        int a = 20;
        int b = 12;
    
        printf("a = %d, b = %d\n", a, b);
        printf("a + b = %d\n", add(a, b));
        printf("a - b = %d\n", subtract(a, b));
        printf("a * b = %d\n", multiply(a, b));
    
        return 0;
    }

    并將靜態(tài)庫 libcalc.a 置于同級目錄下:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    通過指令$ gcc main.c -o main -L ./ -l calc編譯 main.c 文件,并鏈接靜態(tài)庫 libcalc.a:

    • -L:指定使用的庫的路徑(因?yàn)樵谕患壞夸浵?,所以可以直接用?code>./,或者使用絕對路徑也是可以的)

    • -l:指定使用的庫(庫的名字一定要掐頭去尾。如:libcalc.a 變?yōu)?calc)

    編譯結(jié)果會提示三個 warning,這是由于沒有定義這些函數(shù)導(dǎo)致的,先暫時不用管。

    運(yùn)行 main 結(jié)果如下:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    我們思考這么一個問題:由于靜態(tài)庫是我們自己制作的,其所包含的函數(shù)我們很清楚,直接鏈接并使用即可。但如果別人想要使用呢?他們可不清楚靜態(tài)庫中的函數(shù)該如何調(diào)用,所以我們有必要提供一個頭文件,這樣將靜態(tài)庫及頭文件交給其他人時,他們知道該如何用了。

    head.h

    #ifndef _HEAD_H_
    #define _HEAD_H_
    
    int add(int a, int b);
    
    int subtract(int a, int b);
    
    int multiply(int a, int b);
    
    #endif

    還記得之前的報錯嗎?現(xiàn)在有了頭文件就要使用起來。

    main.c

    #include <stdio.h>
    #include "head.h"
    
    int main()
    {
        int a = 20;
        int b = 12;
    
        printf("a = %d, b = %d\n", a, b);
        printf("a + b = %d\n", add(a, b));
        printf("a - b = %d\n", subtract(a, b));
        printf("a * b = %d\n", multiply(a, b));
    
        return 0;
    }

    編譯、鏈接、運(yùn)行,一氣呵成:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    2.2.5 ar 命令參數(shù)介紹

    制作靜態(tài)庫時所使用的指令$ ar rcs libcalc.a add.o sub.o mult.o div.o共有三個參數(shù):

    • -c:創(chuàng)建一個庫,不管庫是否存在,都將創(chuàng)建。這個很好理解,就不做過多的解釋了。

    • -r:在庫中插入(替換)模塊 。默認(rèn)新的成員添加在庫的結(jié)尾處,如果模塊名已經(jīng)在庫中存在,則替換同名的模塊。

    • -s:創(chuàng)建目標(biāo)文件索引,這在創(chuàng)建較大的庫時能加快時間。

    參數(shù) -r 的詳細(xì)解釋

    假設(shè)現(xiàn)在有了新的需求,需要靜態(tài)庫 libcalc.a 提供除法運(yùn)算的功能模塊,該怎么操作呢?

    首先我們需要新建一個除法運(yùn)算的源文件 div.c:

    #include <stdio.h>
    
    double divide(int a, int b)
    {
        return (double)a / b;
    }

    并通過匯編操作生成目標(biāo)文件 div.o。

    接下來我們可以通過 -r 參數(shù)將除法運(yùn)算的模塊添加到靜態(tài)庫中:$ ar -r libcalc.a div.o

    并且要在 head.h 中增加對除法運(yùn)算的聲明:

    #ifndef _HEAD_H_
    #define _HEAD_H_
    
    // Other
    
    double divide(int a, int b);
    
    #endif

    參數(shù) -s 的詳細(xì)解釋

    在獲取一個靜態(tài)庫的時候,我們可以通過$ nm -s libcalc.a來顯示庫文件中的索引表:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    而索引的生成就要?dú)w功于 -s 參數(shù)了。

    如果不需要創(chuàng)建索引,可改成 -S 參數(shù)。

    如果 libcalc.a 缺少索引,可以使用$ ranlib libcalc.a 指令添加。

    2.2.6 其他命令介紹
    # 顯示庫文件中有哪些目標(biāo)文件,只顯示名稱
    $ ar t libcalc.a
    
    # 顯示庫文件中有哪些目標(biāo)文件,顯示文件名、時間、大小等詳細(xì)信息
    $ ar tv libcalc.a
    
    # 顯示庫文件中的索引表
    $ nm -s libcalc.a
    
    # 為庫文件創(chuàng)建索引表
    $ ranlib libcalc.a

    2.3 動態(tài)庫

    2.3.1 動態(tài)庫簡介

    在 Linux 中動態(tài)庫以 lib 作為前綴、以 .so 作為后綴,形如 libxxx.so(其中的 xxx 是庫的名字,自己指定即可)。相比于靜態(tài)庫,使用動態(tài)庫的程序,在程序編譯時并不會鏈接到目標(biāo)代碼中,而是在運(yùn)行時才被載入。不同的應(yīng)用程序如果調(diào)用相同的庫,那么在內(nèi)存中只需要有一份該共享庫的實(shí)例,避免了空間浪費(fèi)問題。同時也解決了靜態(tài)庫對程序的更新的依賴,用戶只需更新動態(tài)庫即可。

    2.3.2 動態(tài)庫的生成

    生成動態(tài)庫是直接使用 gcc 命令,并且需要添加 -fpic 以及 -shared 參數(shù):

    • -fpic 參數(shù)的作用是使得 gcc 生成的代碼是與位置無關(guān)的,也就是使用相對位置。

    • -shared 參數(shù)的作用是告訴編譯器生成一個動態(tài)鏈接庫。

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    2.3.3 動態(tài)庫的制作舉例

    還是以上述程序 add.c、sub.c、mult.c 為例:

    # 第一步:將源文件 add.c、sub.c、mult.c 進(jìn)行匯編,得到二進(jìn)制目標(biāo)文件 add.o、sub.o、mult.o
    $ gcc -c -fpic add.c sub.c mult.c
    
    # 第二步:將得到的 .o 文件打包成動態(tài)庫
    $ gcc -shared add.o sub.o mult.o -o libcalc.so
    
    # 第三步:發(fā)布動態(tài)庫和頭文件
    1. 提供頭文件 head.h
    2. 提供動態(tài)庫 libcalc.so

    至于為什么需要提供頭文件,在講解靜態(tài)庫時已經(jīng)做了說明,此處不再贅述。

    2.3.4 動態(tài)庫的使用

    head.h

    #ifndef _HEAD_H_
    #define _HEAD_H_
    
    int add(int a, int b);
    
    int subtract(int a, int b);
    
    int multiply(int a, int b);
    
    #endif

    main.c

    #include <stdio.h>
    #include "head.h"
    
    int main()
    {
        int a = 20;
        int b = 12;
    
        printf("a = %d, b = %d\n", a, b);
        printf("a + b = %d\n", add(a, b));
        printf("a - b = %d\n", subtract(a, b));
        printf("a * b = %d\n", multiply(a, b));
    
        return 0;
    }

    和靜態(tài)庫的鏈接方式一樣,都是通過指令$ gcc main.c -o main -L ./ -l calc來進(jìn)行鏈接庫操作。

    gcc 通過指定的動態(tài)庫信息生成了可執(zhí)行程序 main,但是可執(zhí)行程序運(yùn)行卻提示無法加載到動態(tài)庫:

    ./main: error while loading shared libraries: libcalc.so: cannot open shared object file: No such file or directory

    這是怎么回事呢?

    2.3.5 解決動態(tài)庫加載失敗的問題

    首先來看一下不同庫的工作原理:

    • 靜態(tài)庫如何被加載:

      • 在程序編譯的最后一個階段也就是鏈接階段,提供的靜態(tài)庫會被打包到可執(zhí)行程序中。

      • 當(dāng)可執(zhí)行程序被執(zhí)行,靜態(tài)庫中的代碼也會一并被加載到內(nèi)存中,因此不會出現(xiàn)靜態(tài)庫找不到無法被加載的問題。

    • 動態(tài)庫如何被加載:

      • 程序會先檢測所需的動態(tài)庫是否可以被加載,加載不到就會提示上邊的錯誤信息。

      • 當(dāng)動態(tài)庫中的函數(shù)在程序中被調(diào)用了,這個時候動態(tài)庫才加載到內(nèi)存,如果不被調(diào)用就不加載。

      • 在程序編譯的最后一個階段也就是鏈接階段,在 gcc 命令中雖然指定了庫路徑,但是這個路徑并沒有被記錄到可執(zhí)行程序中,只是檢查了這個路徑下的庫文件是否存在。同樣對應(yīng)的動態(tài)庫文件也沒有被打包到可執(zhí)行程序中,只是在可執(zhí)行程序中記錄了庫的名字。

      • 當(dāng)可執(zhí)行程序被執(zhí)行起來之后:

    動態(tài)庫的檢測和內(nèi)存加載操作都是由動態(tài)鏈接器來完成的

    動態(tài)鏈接器是一個獨(dú)立于應(yīng)用程序的進(jìn)程,屬于操作系統(tǒng)。當(dāng)用戶的程序需要加載動態(tài)庫的時候動態(tài)連接器就開始工作了,很顯然動態(tài)連接器根本就不知道用戶通過 gcc 編譯程序的時候通過參數(shù) -L 指定的路徑。

    那么動態(tài)鏈接器是如何搜索某一個動態(tài)庫的呢,在它內(nèi)部有一個默認(rèn)的搜索順序,按照優(yōu)先級從高到低的順序分別是:

    • 可執(zhí)行文件內(nèi)部的 DT_RPATH 段。

    • 系統(tǒng)的環(huán)境變量 LD_LIBRARY_PATH。

    • 系統(tǒng)動態(tài)庫的緩存文件 /etc/ld.so.cache。

    • 存儲「靜態(tài)庫 / 動態(tài)庫」的系統(tǒng)目錄 /lib、/usr/lib 等。

    按照以上四個順序,依次搜索,找到之后結(jié)束遍歷。若檢索到最終還是沒找到,那么動態(tài)連接器就會提示動態(tài)庫找不到的錯誤信息。一般情況下,我們都是通過修改系統(tǒng)的環(huán)境變量的方式設(shè)置動態(tài)庫的地址。

    將動態(tài)庫路徑追加到環(huán)境變量 LD_LIBRARY_PATH 中:$ LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:動態(tài)庫的絕對路徑

    比如,我所需要的動態(tài)庫的絕對路徑為 /mnt/hgfs/SharedFolders/DynamicLibrary,那么:

    $ LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/mnt/hgfs/SharedFolders/DynamicLibrary

    這樣的話,我在運(yùn)行 main,就不會報錯了。

    但是通過這種方式設(shè)置的環(huán)境變量盡在當(dāng)前的終端中有效,那么怎樣才能讓這個設(shè)置永久生效呢?

    通過指令$ vim ~/.bashrc打開并修改該文件:

    GCC指令及動態(tài)庫、靜態(tài)庫怎么使用

    修改后,使用$ source ~/.bashrc使修改立即生效。

    經(jīng)過上述操作,就不用每次開啟終端都需要修改環(huán)境變量了。當(dāng)然這種永久生效的方式僅適用于動態(tài)庫路徑唯一的情況,如果你每次使用的動態(tài)庫都在不同的位置,那么這么設(shè)置也沒啥用????

    2.4 動態(tài)庫與靜態(tài)庫的比較

    2.4.1 靜態(tài)庫的特點(diǎn)
    • 靜態(tài)庫對函數(shù)庫的鏈接是在編譯期完成的。

    • 靜態(tài)庫在程序編譯時會鏈接到目標(biāo)代碼中,因此使可執(zhí)行文件變大。

    • 當(dāng)鏈接好靜態(tài)庫后,在程序運(yùn)行時就不需要靜態(tài)庫了。

    • 對程序的更新、部署與發(fā)布不方便,需要全量更新。

    • 如果某一個靜態(tài)庫更新了,所有使用它的應(yīng)用程序都需要重新編譯、發(fā)布給用戶。

    2.4.2 動態(tài)庫的特點(diǎn)
    • 動態(tài)庫把對一些庫函數(shù)的鏈接載入推遲到程序運(yùn)行時期。

    • 可以實(shí)現(xiàn)進(jìn)程之間的資源共享,因此動態(tài)庫也稱為共享庫。

    • 將一些程序升級變得簡單,不需要重新編譯,屬于增量更新。

    2.5 使用庫的目的

    在項(xiàng)目中使用庫一般有兩個目的:

    • 為了使程序更加簡潔不需要在項(xiàng)目中維護(hù)太多的源文件。

    • 另一方面是為了源代碼保密,畢竟不是所有人都想把自己編寫的程序開源出來。

    當(dāng)我們拿到了庫文件(動態(tài)庫、靜態(tài)庫)之后要想使用還必須有這些庫中提供的 API 函數(shù)的聲明,也就是頭文件,把這些都添加到項(xiàng)目中,就可以快樂的寫代碼了。

    讀到這里,這篇“GCC指令及動態(tài)庫、靜態(tài)庫怎么使用”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點(diǎn)還需要大家自己動手實(shí)踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    gcc
    AI