Overview of Content
本文深入介紹了C語言庫的使用及相關知識。首先,我們將概述庫的概念,包括靜態庫(.a
檔案)和動態庫(.so
檔 案)。然後,我們將深入探討如何使用這些庫,包括如何編譯動態庫以及如何編譯靜態庫並進行靜態連結。我們還將討論如何查詢系統庫函數以及使用 gcc 手動連結系統不常用的庫。
接著,我們將探討手工製作C語言庫的過程,包括製作靜態庫和動態庫。我們還將介紹使用"nm"命令查看靜態庫符號以及使用"ldd"命令查看動態庫依賴和位置的方法。
最後,我們將進一步探討有關庫的更多知識,包括 ld.so
如何查找庫的順序以及如何在編譯時設定共享庫的位置。
通過本文,讀者將全面了解C語言庫的使用和編譯過程,並掌握有關靜態庫和動態庫的重要知識。
C Library 認識
簡單來說就是為了要覆用函數,所以產生出了 Library;如常見的就有 glibc
Library 庫又分為 1. 靜態庫、2. 動態庫
● 為何不用
Open Source
就好了呢 ?因為無法商業化,廠商如果要保全自己開發的程式,最安全的方式就是提用 Library 庫,這樣既可以得到商業利益,又可以提供給使用者
靜態 Library 庫 - .a
● 靜態 Library 庫出現的比較早,附檔名為 .a
,它的概念如下:
A. 將原程式 經過匯編,但不進行連結
B. 透過 ar
工具,將許多 .o
文件歸檔至 .a
文件
C. 提供給使用者 .o
文件、.h
頭文件,就可以直接使用
● 每個使用到靜態 Library 的應用程式,都會複製一份檔案到應用中
這個缺點是
A. 佔用空間,較率較低
B. 在更新 library 時,每個依賴於靜態 library 的程式都必須重新編譯(因為他們都獨自有一個完整的靜態檔案在自己的空間)
動態 Library 庫 - .so
● 動態 Library 庫,附檔名為 .so
,它的效率更高,概念如下:
A. .so
中存的是 鏈結標記,在需要時才去記憶體中尋找
B. .so
在設備中只存在一份在記憶體中,類似於一個虛擬文件,應用在使用時,才去記憶體中尋找對應的標記
C. 當 .so
更新時,不需要每個應用都重新編譯(除非 .so
有新的符號連結)
C Library 使用
在使用 Library 時有幾個步驟需要注意
A. 引入 Library 的頭文件 (.h
檔案)
B. 有些函數鏈結時需要使用 -l
+ xxx 的方式指定具體連結的 Library
C. 動態 Library 大部分會使用 -L
來指定動態 Library 存放的 目錄
編譯動態庫 - 動態連結
以下來動態連結系統庫
#include <stdio.h>
int main(void) {
printf("Hello C library.");
return 0;
}
● 一般我們用 gcc
命令來編譯 .c
文件時,預設使用動態 Library 鏈結:所以編譯出的檔案大小並不大 (因為只有鏈結符號)
gcc Hello.c -o Hello_1.out
大小為:
16,064
Byte
編譯靜態庫 - 靜態連結 static
以下來靜態連結系統庫
#include <stdio.h>
int main(void) {
printf("Hello C library.");
return 0;
}
● 透過選項 -static
,限定 gcc 使用靜態鏈結:可以發現編譯出來的執行檔相當大,因為靜態鏈結會將所有資料都複製進應用中
gcc Hello.c -static -o Hello_2.out
大小為:
896,216
Byte
● 編譯時有時不需要標頭檔 ?
其實都是需要標頭檔 (
.h
) 的,只是有些標頭檔已經存在系統中,可以在系統中發現,所以不用特別指定像是:
stdio.h
標頭檔就是系統內建,所以不自己手動需要添加
查詢系統 Library 函數
● 可以透過 man 3 <API 名稱>
查看相關資料,指令中的 3
代表 section,是查看 library 如何使用,而其他數字代表的意思請見下圖
man man
A. Library calls 查看 memcpy
:可以看到該函數的原形,要引入哪個標頭檔... 等等
man 3 memcpy
B. System calls 查看 mmp
man 2 mmap
gcc 手動鏈結 - 系統不常用函式庫
#include <stdio.h>
#include <math.h>
int main(void) {
int a = 100;
double b = sqrt(a);
printf("result: %lf\n", b);
return 0;
}
編譯時出錯,找不到
sqrt
函數
● Library calls 查看 sqrt (平方),在檔案編譯時需要使用 -lm
去做鏈結才能正常編譯成功,因為鏈結器預設會去找常用的 Library
<math.h>
並不常用,所以 編譯時需手動鏈結
man 3 sqrt
● 修正編譯指令,使用 -lm
告訴編譯器去 libm 中尋找函數
gcc Math_Test.c -lm -o Math_Test.out
這裡就符合上面提到的特殊 Library 需要使用
-lxxx
去做鏈結
Hand made C Library
透過 Linux 提供的工具就可以自己來手動包動、靜態 library
Hand made - static library
打包工具使用
ar
● 靜態 Library Source:主要分為兩個重點;1. 程式實作它最終會被編譯成二進制文件、另一個則是辨識二進制文件的 2. 頭文件
A. 程式實作:MyStaticLib.c
// MyStaticLib.c
int add(int a, int b) {
return a + b;
}
static int _sub(int a, int b, int reverse) {
if(reverse) {
return b - a;
} else {
return a - b;
}
}
int sub_1(int a, int b) {
return _sub(a, b, 0);
}
int sub_2(int a, int b) {
return _sub(a, b, 1);
}
B. 頭文件:MyHead.h
// MyHead.h
int add(int a, int b);
int sub_1(int a, int b);
int sub_2(int a, int b);
● 編譯打包成靜態 Library
A. 匯編 Source Code (必須在匯編階段,所以使用 -c
option 指定)
gcc -c MyStaticLib.c -o MyStaticLib.o
B. 使用 ar -rc
集成靜態 Library
## ar -rc <lib 名稱> <.o 檔案_1> <.o 檔案_2> ...
ar -rc libMyStatic.a MyStaticLib.o
● 使用靜態 Library:引入 Library 頭檔案
#include "MyHead.h" // 引入 Library 頭檔案
#include <stdio.h>
int main(void) {
int a = 10, b = 20;
// 直接使用 Library
printf("a - b: %d\n", add(a, b));
printf("a - b: %d\n", sub_1(a, b));
printf("b - a: %d\n", sub_2(a, b));
return 0;
}
A. 嘗試編譯:透過上面的學習我們可以知道要找到目標 library 必須使用 -lxxx
鏈結,這裡我們使用以下指令
● 使用
-l
鏈結.a
時:去除前綴lib
& 副檔名.so
e.g:鏈結
libMyStatic.a
就需要寫成-lMyStatic
來鏈結
## -l<目標 Library>
## -l <目標 Library> (中間有沒有空格都可以)
gcc UseStaticLib.c -l MyStatic -o UseStaticLib.out
編譯失敗:因為在預設庫資料夾中,找不到
MyStatic
庫
B. 添加指定路徑後再次編譯:
透過提示我們可以知道編譯器在 /usr/bin/ld
目錄下找不到目標鏈結,在這裡我們就必須使用 -L
來指定 Library 位置
## -L<目標 Library 位置>
## -L <目標 Library 位置> (中間有沒有空格都可以)
gcc UseStaticLib.c -l MyStatic -L . -o UseStaticLib.out
● 執行編譯好的 UseStaticLib.out
檔案
./UseStaticLib.out
nm - 查看靜態 Library 符號
● 透過 nm
指令就可以查看靜態 Library 符號
nm MyStaticLib.a
Hand made - dynamic library
● 動態鏈結檔在不同平台下有不同的附檔名
| Window | Linux |
| -------- | -------- |
| .dll | .so |
● 程式就使用上面靜態 Library Source Code
● 發布動態 Library 時,依樣要發布 Header 檔案
● 編譯 .so
動態 Library
A. 我們將上面的 MyStaticLib.c
再次匯編,並指定檔案名稱為 MySharedLib.o
gcc -c MyStaticLib.c -o MySharedLib.o -fPIC
●
-fPIC
表示位置將該檔案編譯成 位置無關碼
B. 編譯成動態 Library libMyShared.so
gcc MySharedLib.o -shared -o libMyShared.so
● 編譯應用使用的檔案 UseSharedLib.c
(內容同靜態檔案,只是複製過來並改名)
gcc UseSharedLib.c -L . -l MyShared -o UseSharedLib.out
● 運行依賴動態檔案的應用出錯 ??!
因為在運行時才加載進 RAM 中,但加載 lib 的位置
/usr/lib
找不到目標檔案
● 解決方式有幾種
A. 將 libMyShared.so
放置到 /usr/lib
目錄下
B. 修改變量區域變量 LD_LIBRARY_PATH
,串上 so 所在的資料夾
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD && ./UseSharedLib.out
C. 在編譯時決定運行時要尋找的庫的位置(-Wl,-rpath
下面有範例)
ldd - 查看動態 Library 依賴/位置
● 使用 ldd
指令就可以查看動態 Library 的依賴
ldd UseSharedLib.out
● 查找系統應用的動態連結關係
ldd /bin/bash
使用 Pair 的方式去看:左邊是連接的動態庫名,右邊則是該動態庫真實位置
Pair<庫名, 該庫的位置>
● 最後一行的
/lib/ld-linux-aarch64.so.1
標明了該指令 ldd 的ls.so
實際位置
Library 庫的更多知識
ld.so
找函式庫 - 順序
● 共享函式庫強大,但它卻是管理困難、連接複雜的,我們往往在使用時會遇到以下困難
A. 作為應用使用者:該應用需要什麼函式庫
可以使用
ldd
命令查看該應用所需的函示庫
B. 作用應用開發者:如何找到共享函示庫
C. 如何連接函式庫
● ld.so
尋找函式庫的方式
A. 查看 LD_LIBRARY_PATH
環境變量是否有賦予值,如果有的話 ld.so
會優先尋找該目錄下的庫
B. 看看要運行的應用是否有 預先設定好的執行時函式庫搜尋路徑(runtime library search path
)
使用
-Wl,-rpath
設定,下面小節有範例
C. 參考系統當前的 快取 /etc/ld.so.cache
cat /etc/ld.so.cache | sed 's/.so/.so\n/g'
●
/etc/ld.so.cache
的來源它的來源是
/etc/ld.so.conf
目錄列表,而它的內容又會指向/etc/ld.so.conf.d
目錄,所以是該目錄下所有 config 檔案
● 如果有改動該目錄,需使用以下命令去刷新
ldconfig -v
不過建議不要肆意添加內容進去,容易導致混淆的風險
編譯時設定 - 共享庫的位置
● 編譯時設定共享 (.so
) 函式庫的路徑,範例如下
A. 創建共享函示庫(libhello.so)
● 共享函式庫標頭檔 hello.h
// hello.h
#ifndef HELLO
#define HELLO
void sayHello(void);
#endif
● 共享函式庫源碼 hello.c
// hello.c
#include <stdio.h>
void sayHello(void) {
printf("Hello world, ln~ \n");
}
● 編譯 -c
與位置無關代碼 -fPIC
gcc -c hello.c -o hello.o -fPIC
● 編譯共享庫 libhello.so
gcc -shared hello.o libhello.so
B. 透過標頭檔使用 libhello.so
共享庫
#include "./hello.h"
int main(void) {
sayHello();
return 0;
}
C. 連接 libhello.so
共享庫:使用 -Wl,-rpath
選項對連接器傳入設定,設定共享庫的路徑為當位置(以這個例子來說是這樣的)
cc main.c -o main.o -Wl,-rpath="$PWD" -L . -lhello
./main.o
最後使用 ldd
命令進行驗證,確定連接的位置就是我們指定的目錄
更多的 C 語言相關文章
關於 C 語言的應用、研究其實涉及的層面也很廣闊,但主要是有關於到系統層面的應用(所以 C 語言又稱之為系統語言),為了避免文章過長導致混淆重點,所以將文章係分成如下章節來幫助讀者更好地從不同的層面去學習 C 語言
C 語言基礎
● C 語言基礎:有關於到 C 語言的「語言基礎、細節」
編譯器、系統開念
● 編譯器、系統開念:是學習完 C 語言的基礎(或是有一定的程度)之後,從編譯器以及系統的角度重新檢視 C 語言的一些細節
C 語言與系統開發
● C 語言與系統開發:在這裡會說明 C 語言的實際應用,以及系統為 C 語言所提供的一些函數、庫... 等等工具,看它們是如何實現、應用