計算機系統篇之鏈接(3):靜態鏈接(上)

計算機系統篇之鏈接(3):靜態鏈接(上)

Author:stormQ

Saturday, 21. December 2019 11:58AM


引入靜態庫的動機

引入靜態庫是爲了更好地解決“編譯器開發者如何將標準庫函數提供給調用者使用”的問題。

解決該問題的不同方式及比較如下所示:

解決方式 優點 缺點
方式1:編譯器識別程序中對標準庫函數的調用並直接生成相應的代碼
  • 對調用者而言,最方便。因爲標準庫函數總是可用的,不需要調用者做任何事情。
  • 增加編譯器的複雜性。因爲如果採用這種方式,編譯器的開發者需要識別程序中調用了哪些標準庫函數,並直接生成相應的代碼。由於 C 標準庫函數很多,所以這種方式將大大增加編譯器的複雜性。
  • 不利於編譯器升級維護。因爲對標準庫函數的增、刪、改,都要發佈一個新版本的編譯器。
  • 方式2:將所有的標準庫函數實現放到同一個可重定位目標文件中(即 .o 文件)
  • 將標準庫函數的實現與編譯器的實現解耦。
  • 對調用者而言,比較方便。因爲調用者只需要額外鏈接一個可重定位目標文件。
  • 浪費磁盤空間。每個可執行目標文件都包含一份完整的標準庫函數實現的拷貝,即使有些庫函數並沒有用到。
  • 浪費內存空間。每個程序執行時都會拷貝一份完整的標準庫函數實現到內存中,即使有些庫函數並沒有用到。
  • 增加庫函數開發者開發和維護的成本。對任意庫函數的任何改動,都需要重新編譯所有的庫函數實現。
  • 方式3:將每個標準庫函數實現放到一個獨立的可重定位目標文件中,即一個可重定位目標文件中只存放一個庫函數的實現
  • 標準庫函數實現與編譯器實現解耦,從而解決了方式1的缺點。
  • 只需要重新編譯修改過的庫函數,同時避免了未使用的庫函數佔用磁盤和內存空間的問題,從而解決了方式2的缺點。
  • 對調用者而言,最不方便。調用者需要手動鏈接所有需要的可重定位目標文件,這一過程容易出錯並且花費比較長的時間。
  • 方式4:靜態庫
  • 在鏈接期,鏈接器從靜態庫中只拷貝程序需要的可重定位目標文件。從而避免了未用到的庫函數的磁盤和內存空間浪費。
  • 對調用者而言,比較方便。調用者只需要鏈接少量的靜態庫,不容易出錯且節省時間。
  • 軟件維護不簡易。如果靜態庫升級了,那麼可執行目標文件必須顯式地與更新了的靜態庫重新鏈接。
  • 磁盤空間仍有一定的浪費。如果靜態庫以靜態鏈接的方式(即鏈接時添加--static選項)生成可執行目標文件,那麼不同的可執行目標文件中可能存在相同的可重定位目標文件,從而造成一定的磁盤空間浪費。
  • 內存空間仍有一定的浪費。不同的可執行目標文件中可能存在相同的可重定位目標文件,這些相同的可重定位目標文件會被拷貝到其運行進程的代碼段中,從而造成一定的內存空間浪費。
  • 如何生成靜態庫

    靜態庫的文件格式被稱爲存檔(archive),以.a爲後綴。archive 是一組連接起來的可重定位目標文件的集合,有一個頭部用於描述每個成員可重定位目標文件的大小和位置。

    生成靜態庫的命令:

    $ ar rs <target static library> <object file 1> <object file n>
    # $ ar rs libtest.a sum.o test.o
    

    注:操作碼r表示將指定的可重定位目標文件插入到靜態庫中,如果沒有靜態庫則創建。修飾碼s表示將索引(ar爲可重定位目標文件的每個符號創建一個索引)添加到歸檔文件中,或者更新它(如果它已經存在)。建立索引的目的:可以加速到庫的鏈接,並允許庫中的函數相互調用,而不考慮它們在歸檔文件中的位置。

    1)查看靜態庫中有哪些可重定位目標文件

    # 查看靜態庫中所有的可重定位目標文件
    $ ar t /usr/aarch64-linux-gnu/lib/libc.a
    # 查看靜態庫中指定的可重定位目標文件
    $ ar t /usr/aarch64-linux-gnu/lib/libc.a printf.o scanf.o
    

    注:操作碼t只顯示可重定位目標文件的名稱。如果要顯示其他信息,比如:可重定位目標文件的大小、文件權限等,需要添加修飾碼v,即$ ar tv /usr/aarch64-linux-gnu/lib/libc.a

    2)從靜態庫中提取可重定位目標文件(即將指定的可重定位目標文件從靜態庫中拷貝到磁盤上)

    # 從靜態庫中提取所有的可重定位目標文件
    $ ar x /usr/aarch64-linux-gnu/lib/libc.a
    # 從靜態庫中提取指定的可重定位目標文件
    $ ar x /usr/aarch64-linux-gnu/lib/libc.a printf.o scanf.o
    

    3)從靜態庫中刪除指定的可重定位目標文件

    $ ar d /usr/aarch64-linux-gnu/lib/libc.a printf.o scanf.o
    

    4)查看ar爲可重定位目標文件中的符號創建的索引

    $ nm --print-armap libtest.a
    
    Archive index:
    _Z3sumii in sum.o
    _Z4funcv in test.o
    
    sum.o:
    0000000000000000 T _Z3sumii
    
    test.o:
                     U g_val_1
                     U g_val_2
    0000000000000000 T _Z4funcv
    

    如何使用靜態庫

    1)查看源文件的源碼

    $ cat sum.cpp 
    int sum(int a, int b)
    {
      return a + b;
    }
    $ cat test.cpp 
    extern int g_val_1;
    extern int g_val_2;
    
    void func()
    {
      g_val_1 *= 2;
      g_val_2 *= 2;
    }
    $ cat main.cpp 
    #include <stdio.h>
    
    int g_val_1;
    int g_val_2 = 3;
    
    void func();
    
    int main()
    {
      printf("original value: g_val_1=%d, g_val_2=%d\n", g_val_1, g_val_2);
      func();
      printf("now value: g_val_1=%d, g_val_2=%d\n", g_val_1, g_val_2);
      return 0;
    }
    

    2)生成可重定位目標文件

    $ g++ -c test.cpp sum.cpp
    

    注:生成可重定位目標文件分別爲test.osum.o

    3)生成靜態庫

    $ ar rs libtest.a test.o sum.o
    

    4)使用靜態庫

    方式1:

    # main 可以直接加載到內存並運行,在加載時無需更進一步的鏈接
    $ g++ -o main main.cpp --static ./libtest.a
    

    方式2:

    # main_2 可以直接加載到內存並運行,在加載時無需更進一步的鏈接
    $ g++ -o main_2 main.cpp --static -L. -ltest
    

    方式3:

    # main_3 在加載時需要更進一步的鏈接
    $ g++ -o main_3 main.cpp ./libtest.a
    
    • 注:

      ---static選項指示編譯器驅動程序,鏈接器應該構建一個完全鏈接的可執行目標文件,它可以直接加載到內存並運行,在加載時無需更進一步的鏈接。

      --ltestlibtest.a的縮寫。

      --L.指示鏈接器在當前目錄下查找libtest.a

    5)運行可執行目標文件

    $ ./main
    original value: g_val_1=0, g_val_2=3
    now value: g_val_1=0, g_val_2=6
    $ ./main_2
    original value: g_val_1=0, g_val_2=3
    now value: g_val_1=0, g_val_2=6
    $ ./main_3
    original value: g_val_1=0, g_val_2=3
    now value: g_val_1=0, g_val_2=6
    

    如果你覺得本文對你有所幫助,歡迎關注公衆號,支持一下!

    在這裏插入圖片描述

    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章