首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux 下 C 語言共用程式庫 (.so) 製作完整指南:從原始碼到動態載入

     編輯:關於C語言

共用程式庫核心概念

  • ELF 格式:Linux 共用庫為 ELF (Executable and Linkable Format),已具備可執行條件,載入時由動態連結器 ld-linux.so 完成重定位。
  • 程式碼段共享,資料段私有:多行程映射同一 .so 的 .text 區段至相同物理頁框 (Copy-on-Write),.data/.bss 各行程獨立副本。
  • 位置無關程式碼 (PIC):編譯時加 -fPIC,產生相對定址指令,使庫可在任意虛擬位址載入,實現真正共享。
  • 版本管理 (SONAME):libfoo.so.1 (實體) → libfoo.so (連結器用符號連結) → libfoo.so.1.2.3 (完整版本),支援多版本共存與平滑升級。

完整製作流程實戰

步驟 1:編寫原始碼

// my_strcpy.c
#include <string.h>
void my_strcpy(char *dest, const char *src)
{
    while ((*dest++ = *src++));
}

// my_strcmp.c
int my_strcmp(const char *s1, const char *s2)
{
    while (*s1 && *s2) {
        int diff = *s1 - *s2;
        if (diff) return diff;
        s1++; s2++;
    }
    return *s1 - *s2;
}

步驟 2:編譯為位置無關目標檔

gcc -fPIC -c my_strcpy.c my_strcmp.c
# 產生 my_strcpy.o, my_strcmp.o

-fPIC 關鍵:生成 GOT (Global Offset Table) 相對定址,否則共用庫載入固定位址會衝突。

步驟 3:建立共用庫並指定 SONAME

gcc -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.0.0 my_strcpy.o my_strcmp.o
  • -shared:產生共用庫而非執行檔。
  • -Wl,-soname,libmylib.so.1:傳遞 -soname 給連結器 ld,嵌入 .dynamic 區段,動態連結器載入時讀取此字串尋找庫檔。
  • -o libmylib.so.1.0.0:實體檔名含完整版本。

步驟 4:建立連結器與執行期符號連結

ln -sf libmylib.so.1.0.0 libmylib.so.1   # SONAME 連結,執行期載入用
ln -sf libmylib.so.1 libmylib.so         # 連結器名稱,編譯期 -lmylib 用

步驟 5:編譯測試程式並連結

// main.c
#include <stdio.h>
void my_strcpy(char*, const char*);
int my_strcmp(const char*, const char*);

int main() {
    char buf[32];
    my_strcpy(buf, "hello linux");
    puts(buf);
    printf("cmp: %d\n", my_strcmp("hello", "world"));
    return 0;
}

gcc -o main main.c -L. -lmylib

步驟 6:執行期載入路徑解決

直接 ./main 會報 error while loading shared libraries: libmylib.so.1: cannot open shared object file。三種解法:

方法指令適用場景
環境變數 LD_LIBRARY_PATHexport LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH開發/測試臨時用,不污染系統
/etc/ld.so.conf.d/ + ldconfigecho "/opt/mylib" > /etc/ld.so.conf.d/mylib.conf && ldconfig系統級部署,需 root
編譯時嵌入 RPATH/RUNPATHgcc -Wl,-rpath,'$ORIGIN' -o main main.c -L. -lmylib隨程式部署,相對路徑 $ORIGIN 指向執行檔目錄

進階主題

符號可見性控制

// 隱藏內部符號,僅匯出公用 API
#define API_EXPORT __attribute__((visibility("default")))
#define API_INTERNAL __attribute__((visibility("hidden")))

API_EXPORT void my_strcpy(char*, const char*);
API_INTERNAL void internal_helper();

編譯加 -fvisibility=hidden,預設隱藏,僅標記 API_EXPORT 者匯出,減少符號表大小、避免名稱衝突、提升載入速度。

版本化符號 (Symbol Versioning)

// mylib.map
LIBMYLIB_1.0 {
    global:
        my_strcpy;
        my_strcmp;
    local: *;
};
gcc -shared -Wl,--version-script=mylib.map -o libmylib.so.1.0.0 ...

允許同一庫未來釋出 LIBMYLIB_2.0 新增/變更介面,舊執行檔繼續綁定 1.0 符號,實現 ABI 向後相容。

建構系統整合 (CMake / Meson)

# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(mylib LANGUAGES C)
add_library(mylib SHARED my_strcpy.c my_strcmp.c)
set_target_properties(mylib PROPERTIES
    VERSION 1.0.0
    SOVERSION 1
    POSITION_INDEPENDENT_CODE ON
    C_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN ON
)
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
install(TARGETS mylib LIBRARY DESTINATION lib)

常見錯誤排查

現象原因解決
編譯通過,執行找不到 .so執行期載入路徑未包含庫目錄設定 LD_LIBRARY_PATH、rpath 或 ldconfig
undefined symbol: xxx缺少目標檔、符號被隱藏、C++ name mangling檢查 nm -D libmylib.so,確認 extern "C"
版本衝突:多個 libfoo.so.1系統目錄與專案目錄同時存在不同版本用 rpath 鎖定專案私有庫,或改 SONAME
PIC 相關錯誤未加 -fPIC 直接建立 .so所有目標檔必須 -fPIC 編譯

結語

Linux 共用庫製作看似步驟繁瑣,實則每一步都對應動態連結器運行機制的具體需求:-fPIC 解決位址獨立、SONAME 解決版本識別、RPATH 解決部署路徑、符號可見性解決封裝與效能。掌握這些原理,配合 CMake/Meson 現代建構系統,即可產出專業級、可維護、ABI 穩定的共用庫,支撐大型系統的模組化與熱更新架構。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved