iT邦幫忙

2025 iThome 鐵人賽

DAY 16
0
Software Development

30 天精通 C 語言建置與除錯:從 Makefile 到 CMake 跨平台實戰系列 第 16

[Day 16] [gcc+make] 靜態函示庫? 動態函示庫? 如何用gcc/make編譯兩者?

  • 分享至 

  • xImage
  •  

這篇你會學到

  • 什麼是函示庫?
  • 靜態函示庫和動態函示庫的差別是什麼?
  • 為什麼要有兩種不同的版本?
  • 完整的前文小回顧

前文小回顧

工具安裝

gcc

gcc 和 make的關聯

makefile介紹

makefile的花式用法

函示庫(Library)

[Day 14] 的時候我們有引入靜態函示庫的概念

函示庫的定義

函式庫 (Library) 可以想像成是一組已經寫好、整理好的程式碼集合,提供了某些功能讓其他程式可以直接使用,而不必每次都重新實作
例如C語言中的 stdio.h, string.h, time.h, stdlib.h (標頭檔 header file) → 包含在 GNU C Library (glibc) 的 libc.a (靜態庫)/ libc.so (動態庫) 裡面
https://sourceware.org/glibc/manual/latest/pdf/libc.pdf
https://en.wikipedia.org/wiki/C_standard_library
libc.a 包含什麼?

  • stdio.h 宣告的實作 → 像 printf()、scanf()、fopen()、fread()、fwrite()...
  • string.h 宣告的實作 → memcpy()、strlen()、strcpy() ...
  • time.h 宣告的實作 → time()、clock() …
  • stdlib.h 宣告的實作 → malloc()、free()、atoi() …
    但是常用的 math.h 他的函示庫是libm.a
    所以你可能有看過
gcc main.c -o main -lm

後面的 main -lm告訴編譯器要去連結 libm.a (或 libm.so)

函示庫分類

靜態庫.a (archive)
  • .a 是 archive file 的縮寫
  • 本質上是把一堆編譯後的 .o 物件檔打包在一起 可以對照[Day 14] 的內容一起看就會有比較深刻的體會
  • 連結時,編譯器會先觀察你寫的程式有哪些符號 (symbol),例如 printf,去 .a 裡面找哪個 .o 定義了這個符號,再從 .a 裡挑出需要的函式,複製進執行檔
  • 舉例來說
    • 假設 libc.a 裡面有這些物件檔
      printf.o
      scanf.o
      malloc.o
      time.o
      
    • 妳的程式這樣寫的
      #include <stdio.h>
      int main() {
          printf("Hello\n");
          return 0;
      }
      
    • libc.a 可以想像成含有很多.o包起來的倉庫 的連結器只會從 libc.a 把 printf.o 拉進去,不會拉 malloc.otime.o,所以執行檔裡面只包含需要的東西
    • 但是如果是 .a 裡的設計是 .o 包含很多函式(例如 stdio.o 同時有 printfscanffopen),只要用到其中一個函式,整個 stdio.o 都會被拉進來
    • 如果你在執行下面的指令的時候,你可以想像成 連結 libc.a(裡面是.o的集成會根據妳的函示去選擇適配的.o),生成一個完全獨立、不需要外部依賴的可執行檔
    gcc main.c -static -o main
    
動態庫.so(shared object)
  • .so 是 shared object 的縮寫
  • 本質上跟 .a 一樣,裡面也是一堆編譯好的 .o 檔,但它設計成在 執行時 (runtime) 才載入
  • 編譯時,編譯器只會把介面 (symbol) 連結資訊寫進去,不會把實作塞進執行檔
  • 程式執行的時候,作業系統(透過動態載入器 ld.so)才會把 .so 載入記憶體,並把符號指到正確的函式
  • 舉例來說
    • 你寫的程式:
      #include <stdio.h>
      int main() {
          printf("Hello\n");
          return 0;
      }
      
    • 編譯如果沒加 -static:
      gcc main.c -o main
      
      預設會使用 動態連結,產生的執行檔會記錄需要 libc.so 的 printf這樣的資訊,而不是把 printf.o 搬進去, (.a會把整個搬進去)
    • 執行時,系統會去 /lib 或 /usr/lib 找 libc.so.6,載入後才讓 main 能正確呼叫 printf 也就是要執行的系統裏頭本身要有libc.so.6
    • 優點
      • 執行檔體積小(不需要包含函式庫的全部程式碼)
      • 多個程式可以共用同一個 .so,節省記憶體與磁碟空間
      • 更新函式庫只要換掉 .so,不用重編所有程式(只要介面不變)
    • 缺點
      • 執行時必須能找到 .so 檔,不然會報錯 error while loading shared libraries
      • 不同版本 .so 可能會造成相容性問題
      • 如果想確認程式用了哪些 .so,可以用:
      • ldd main
        • 它會列出執行檔依賴的所有動態函式庫,例如:
          linux-vdso.so.1 (0x00007ffd8a5d9000)
          libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4d8b3a0000)
          /lib64/ld-linux-x86-64.so.2 (0x00007f4d8b7a0000)
          
為什麼會有兩種 (.a vs .so)?
  • 如果你要 可攜性(放到沒裝 glibc 的系統上也能跑),就用 .a → 靜態連結。
  • 如果你要 節省空間、方便更新,就用 .so → 動態連結。
  • 這就是為什麼 glibc 同時提供:
/usr/lib/x86_64-linux-gnu/libc.a
/lib/x86_64-linux-gnu/libc.so.6
編譯時的差異

glibc 提供兩種版本,讓你根據需求選用

  • .a = 靜態庫,編譯時打包進去
  • .so = 動態庫,執行時載入
靜態庫 .a vs 動態庫 .so 差異總覽
  • 一次打包、到哪都能跑 → 選 .a
  • 小巧、省空間、方便更新 → 選 .so
特性 靜態庫 .a (archive) 動態庫 .so (shared object)
連結時機 編譯時 (compile time) → 把需要的 .o 直接放進執行檔 執行時 (runtime) → 作業系統載入 .so
執行檔大小 大,因為包含庫的程式碼 小,因為只記錄符號資訊
記憶體使用 每個程式各自擁有一份 多個程式共享同一份 .so
外部依賴 無 → 執行檔獨立,不需額外檔案 有 → 執行檔必須能找到對應的 .so
更新函式庫 需要重新編譯程式 不需重編,只要 .so 介面相容
部署情境 嵌入式系統、單一可攜執行檔 桌面應用程式、伺服器程式
常見檔案 /usr/lib/x86_64-linux-gnu/libc.a /lib/x86_64-linux-gnu/libc.so.6

上一篇
[Day 15] [make] 繼續看懂遞迴make在做什麼...
系列文
30 天精通 C 語言建置與除錯:從 Makefile 到 CMake 跨平台實戰16
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言