---
title: 'C Library 庫'
disqus: kyleAlien
---
C Library 庫
===
## Overview of Content
:::success
* 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
本篇文章對應的是 [**了解 C 語言函式庫 | 靜態、動態函式庫 | 使用與編譯 | Library 庫知識**](https://devtechascendancy.com/understanding-c-library-static-dynamic/)
:::
[TOC]
## C Library 庫 - 概述
簡單來說就是為了要覆用函數,所以產生出了 Library;如常見的就有 `glibc`
Library 庫又分為 ^1.^ **靜態庫**、^2.^ **動態庫**
:::info
* 為何不用 `Open Source` 就好了呢 ?
因為無法商業化,廠商如果要保全自己開發的程式,最安全的方式就是提用 Library 庫,這樣既可以得到商業利益,又可以提供給使用者
:::
### 靜態 Library 庫 - .a
* 靜態 Library 庫出現的比較早,**附檔名為 `.a`**,它的概念如下:
1. 將原程式 **經過匯編,但不進行連結**
2. 透過 `ar` 工具,將許多 `.o` 文件歸檔至 `.a` 文件
3. 提供給使用者 `.o` 文件、`.h` 頭文件,就可以直接使用
> ![](https://i.imgur.com/MUvSr7V.png)
* 每個使用到靜態 Library 的應用程式,都會複製一份檔案到應用中
這個缺點是
1. **佔用空間,較率較低**
2. 在更新 library 時,每個依賴於靜態 library 的程式都必須重新編譯(因為他們都獨自有一個完整的靜態檔案在自己的空間)
> ![](https://i.imgur.com/HqYdxEp.png)
### 動態 Library 庫 - .so
* 動態 Library 庫,**附檔名為 `.so`**,它的效率更高,概念如下:
1. `.so` 中存的是 **鏈結標記**,在需要時才去記憶體中尋找
2. `.so` 在設備中只存在一份在記憶體中,類似於一個虛擬文件,應用在使用時,才去記憶體中尋找對應的標記
3. 當 `.so` 更新時,不需要每個應用都重新編譯(除非 `.so` 有新的符號連結)
> ![](https://i.imgur.com/nEHWh4j.png)
## Library 使用
在使用 Library 時有幾個步驟需要注意
1. 引入 Library 的頭文件 (`.h` 檔案)
2. 有些函數鏈結時需要使用 `-l` + xxx 的方式指定具體連結的 Library
3. 動態 Library 大部分會使用 `-L` 來指定動態 Library 存放的 **目錄**
### 編譯動態庫 - 動態連結
以下來動態連結系統庫
```c=
#include <stdio.h>
int main(void) {
printf("Hello C library.");
return 0;
}
```
* 一般我們用 `gcc` 命令來編譯 `.c` 文件時,**預設使用動態 Library 鏈結**:所以編譯出的檔案大小並不大 (因為只有鏈結符號)
```shell=
gcc Hello.c -o Hello_1.out
```
> 大小為:`16,064` Byte
>
> ![](https://i.imgur.com/0x2Uy3y.png)
### 編譯靜態庫 - 靜態連結 static
以下來靜態連結系統庫
```c=
#include <stdio.h>
int main(void) {
printf("Hello C library.");
return 0;
}
```
* **透過選項 `-static`,限定 gcc 使用靜態鏈結**:可以發現編譯出來的執行檔相當大,因為靜態鏈結會將所有資料都複製進應用中
```shell=
gcc Hello.c -static -o Hello_2.out
```
> 大小為:`896,216` Byte
>
> ![](https://i.imgur.com/sHDwn4y.png)
:::info
* 編譯時有時不需要標頭檔 ?
其實都是需要標頭檔 (`.h`) 的,只是有些標頭檔已經存在系統中,可以在系統中發現,所以不用特別指定
> 像是:`stdio.h` 標頭檔就是系統內建,所以不自己手動需要添加
>
> ![](https://i.imgur.com/lLskPOV.png)
:::
### 查詢系統 Library 函數
* 可以透過 `man 3 <API 名稱>` 查看相關資料,指令中的 `3` 代表 section,是查看 library 如何使用,而其他數字代表的意思請見下圖
```shell=
man man
```
> ![](https://i.imgur.com/HddRyfL.png)
1. Library calls 查看 `memcpy`:可以看到該函數的原形,要引入哪個標頭檔... 等等
```shell=
man 3 memcpy
```
> ![](https://i.imgur.com/ijViQ1T.png)
2. System calls 查看 `mmp`
```shell=
man 2 mmap
```
> ![](https://i.imgur.com/Q0eUKZt.png)
### gcc 手動鏈結 - 系統不常用函式庫
```c=
#include <stdio.h>
#include <math.h>
int main(void) {
int a = 100;
double b = sqrt(a);
printf("result: %lf\n", b);
return 0;
}
```
> 編譯時出錯,找不到 `sqrt` 函數
>
> ![](https://i.imgur.com/ljsEm4n.png)
* Library calls 查看 sqrt (平方),在檔案編譯時需要使用 `-lm` 去做鏈結才能正常編譯成功,因為鏈結器預設會去找常用的 Library
`<math.h>` 並不常用,所以 **編譯時需手動鏈結**
```shell=
man 3 sqrt
```
> ![](https://i.imgur.com/1cUxbqa.png)
* 修正編譯指令,**使用 `-lm` 告訴編譯器去 ++libm++ 中尋找函數**
```shell=
gcc Math_Test.c -lm -o Math_Test.out
```
:::info
這裡就符合上面提到的特殊 Library 需要使用 `-lxxx` 去做鏈結
:::
## Hand made
透過 Linux 提供的工具就可以自己來手動包動、靜態 library
### Hand made - static library
:::info
打包工具使用 `ar`
:::
* **靜態 Library Source**:主要分為兩個重點;^1.^ 程式實作它最終會被編譯成二進制文件、另一個則是辨識二進制文件的 ^2.^ 頭文件
1. **程式實作**:`MyStaticLib.c`
```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);
}
```
2. **頭文件**:`MyHead.h`
```c=
// MyHead.h
int add(int a, int b);
int sub_1(int a, int b);
int sub_2(int a, int b);
```
* 編譯打包成靜態 Library
1. 匯編 Source Code (必須在匯編階段,所以使用 `-c` option 指定)
```shell=
gcc -c MyStaticLib.c -o MyStaticLib.o
```
2. **使用 `ar -rc` 集成靜態 Library**
```shell=
## ar -rc <lib 名稱> <.o 檔案_1> <.o 檔案_2> ...
ar -rc libMyStatic.a MyStaticLib.o
```
> ![](https://i.imgur.com/M1taZwn.png)
* 使用靜態 Library:引入 Library 頭檔案
```c=
#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;
}
```
1. **嘗試編譯**:透過上面的學習我們可以知道要找到目標 library 必須使用 `-lxxx` 鏈結,這裡我們使用以下指令
:::success
* 使用 `-l` 鏈結 `.a` 時:去除前綴 `lib` & 副檔名 `.so`
> e.g:鏈結 `libMyStatic.a` 就需要寫成 `-lMyStatic` 來鏈結
:::
```shell=
## -l<目標 Library>
## -l <目標 Library> (中間有沒有空格都可以)
gcc UseStaticLib.c -l MyStatic -o UseStaticLib.out
```
> 編譯失敗:因為在預設庫資料夾中,找不到 `MyStatic` 庫
2. 添加指定路徑後再次編譯:
透過提示我們可以知道編譯器在 `/usr/bin/ld` 目錄下找不到目標鏈結,在這裡我們就必須使用 `-L` 來指定 Library 位置
```shell=
## -L<目標 Library 位置>
## -L <目標 Library 位置> (中間有沒有空格都可以)
gcc UseStaticLib.c -l MyStatic -L . -o UseStaticLib.out
```
* 執行編譯好的 `UseStaticLib.out` 檔案
```shell=
./UseStaticLib.out
```
> ![](https://i.imgur.com/kpGwuU2.png)
### nm - 查看靜態 Library 符號
* 透過 `nm` 指令就可以查看靜態 Library 符號
```shell=
nm MyStaticLib.a
```
> ![](https://i.imgur.com/a9NySzy.png)
### Hand made - dynamic library
:::info
* 動態鏈結檔在不同平台下有不同的附檔名
| Window | Linux |
| -------- | -------- |
| .dll | .so |
* 程式就使用上面靜態 Library Source Code
* 發布動態 Library 時,依樣要發布 Header 檔案
:::
* 編譯 `.so` 動態 Library
1. 我們將上面的 `MyStaticLib.c` 再次匯編,並指定檔案名稱為 `MySharedLib.o`
```shell=
gcc -c MyStaticLib.c -o MySharedLib.o -fPIC
```
:::success
* `-fPIC`
表示位置將該檔案編譯成 **位置無關碼**
:::
2. 編譯成動態 Library `libMyShared.so`
```shell=
gcc MySharedLib.o -shared -o libMyShared.so
```
> ![](https://i.imgur.com/X9WaPS6.png)
* 編譯應用使用的檔案 `UseSharedLib.c` (內容同靜態檔案,只是複製過來並改名)
```shell=
gcc UseSharedLib.c -L . -l MyShared -o UseSharedLib.out
```
:::warning
* 運行依賴動態檔案的應用出錯 ??!
因為在運行時才加載進 RAM 中,但加載 lib 的位置 `/usr/lib` 找不到目標檔案
> ![](https://i.imgur.com/0YbX3Ua.png)
* 解決方式有幾種
1. 將 `libMyShared.so` 放置到 `/usr/lib` 目錄下
2. 修改變量區域變量 `LD_LIBRARY_PATH`,**串上 so 所在的資料夾**
```shell=
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD && ./UseSharedLib.out
```
> ![](https://i.imgur.com/qkJs482.png)
3. 在編譯時決定運行時要尋找的庫的位置(`-Wl,-rpath` 下面有範例)
:::
### ldd - 查看動態 Library 依賴/位置
* 使用 `ldd` 指令就可以查看動態 Library 的依賴
```shell=
ldd UseSharedLib.out
```
> ![](https://i.imgur.com/3gyHTm4.png)
* 查找系統應用的動態連結關係
```shell=
ldd /bin/bash
```
使用 Pair 的方式去看:**左邊是連接的動態庫名,右邊則是該動態庫真實位置**
> Pair<庫名, 該庫的位置>
>
> ![](https://hackmd.io/_uploads/BycGvfP2n.png)
:::info
* 最後一行的 `/lib/ld-linux-aarch64.so.1` 標明了該指令 ldd 的 `ls.so` 實際位置
:::
## 其他相關知識點
### `ld.so` 找函式庫 - 順序
* 共享函式庫強大,但它卻是管理困難、連接複雜的,我們往往在使用時會遇到以下困難
1. 作為應用使用者:**該應用需要什麼函式庫**
> 可以使用 `ldd` 命令查看該應用所需的函示庫
2. 作用應用開發者:**如何找到共享函示庫**
3. **如何連接函式庫**
* `ld.so` 尋找函式庫的方式
1. **查看 `LD_LIBRARY_PATH` 環境變量是否有賦予值**,如果有的話 `ld.so` 會優先尋找該目錄下的庫
2. 看看要運行的應用是否有 **預先設定好的執行時函式庫搜尋路徑**(`runtime library search path`)
> 使用 `-Wl,-rpath` 設定,下面小節有範例
3. 參考系統當前的 **快取 `/etc/ld.so.cache`**
```shell=
cat /etc/ld.so.cache | sed 's/.so/.so\n/g'
```
> ![](https://hackmd.io/_uploads/H1muqMPnn.png)
:::success
* `/etc/ld.so.cache` 的來源
它的來源是 `/etc/ld.so.conf` 目錄列表,而它的內容又會指向 `/etc/ld.so.conf.d` 目錄,所以是該目錄下所有 config 檔案
> ![](https://hackmd.io/_uploads/B1rzozD22.png)
* 如果有改動該目錄,需使用以下命令去刷新
```shell=
ldconfig -v
```
> 不過建議不要肆意添加內容進去,容易導致混淆的風險
:::
### 編譯時設定 - 共享庫的位置
* 編譯時設定共享 (`.so`) 函式庫的路徑,範例如下
1. **創建共享函示庫(libhello.so)**
* 共享函式庫標頭檔 `hello.h`
```c=
// hello.h
#ifndef HELLO
#define HELLO
void sayHello(void);
#endif
```
* 共享函式庫源碼 `hello.c`
```c=
// hello.c
#include <stdio.h>
void sayHello(void) {
printf("Hello world, ln~ \n");
}
```
* 編譯 `-c` 與位置無關代碼 `-fPIC`
```shell=
gcc -c hello.c -o hello.o -fPIC
```
* 編譯共享庫 `libhello.so`
```shell=
gcc -shared hello.o libhello.so
```
2. **透過標頭檔使用 `libhello.so` 共享庫**
```c=
#include "./hello.h"
int main(void) {
sayHello();
return 0;
}
```
3. **連接 `libhello.so` 共享庫**:使用 `-Wl,-rpath` 選項對連接器傳入設定,設定共享庫的路徑為當位置(以這個例子來說是這樣的)
```shell=
cc main.c -o main.o -Wl,-rpath="$PWD" -L . -lhello
./main.o
```
> ![](https://hackmd.io/_uploads/B1tNzQPnn.png)
最後使用 `ldd` 命令進行驗證,確定連接的位置就是我們指定的目錄
> ![](https://hackmd.io/_uploads/S1nKG7P2n.png)
## 更多的 C 語言相關文章
關於 C 語言的應用、研究其實涉及的層面也很廣闊,但主要是有關於到系統層面的應用(所以 C 語言又稱之為系統語言),為了避免文章過長導致混淆重點,所以將文章係分成如下章節來幫助讀者更好地從不同的層面去學習 C 語言
### C 語言基礎
* **C 語言基礎**:有關於到 C 語言的「語言基礎、細節」
:::info
* [**理解C語言中的位元操作:位元運算基礎與宏定義**](https://devtechascendancy.com/bitwise-operations-and-macros-in-c/)
* [**C 語言解析:void 意義、NULL 意義 | main 函數調用、函數返回值意義 | 臨時變量的產生**](https://devtechascendancy.com/meaning_void_null_return-value_temp-vars/)
* [**C 語言中的 Struct 定義、初始化 | 對齊、大小端 | Union、Enum**](https://devtechascendancy.com/c-struct_alignment_endianness_union_enum/)
* [**C 語言儲存類別、作用域 | 修飾語、生命週期 | 連結屬性**](https://devtechascendancy.com/c-storage-scope-modifiers-lifecycle-linkage/)
* [**指標 & Array & typedef | 指標應用的關鍵 9 點 | 指標應用、細節**](https://devtechascendancy.com/pointers-arrays-const-typedef-sizeof-null/)
:::
### 編譯器、系統開念
* **編譯器、系統開念**:是學習完 C 語言的基礎(或是有一定的程度)之後,從編譯器以及系統的角度重新檢視 C 語言的一些細節
:::warning
* [**理解電腦記憶體管理 | 深入瞭解記憶體 | C 語言程式與記憶體**](https://devtechascendancy.com/computer-memory_manager-c-explained/)
* [**C 語言記憶體區塊規劃 | Segment 段 | 字符串特性**](https://devtechascendancy.com/c-memory-segmentation-string-properties/)
* [**編譯器的角度看程式 | 低階與高階、作業系統、編譯器、直譯器、預處理 | C語言函數探討**](https://devtechascendancy.com/compiler-programming-os-c-functions/)
:::
### C 語言與系統開發
* **C 語言與系統開發**:在這裡會說明 C 語言的實際應用,以及系統為 C 語言所提供的一些函數、庫... 等等工具,看它們是如何實現、應用
:::danger
* [**了解 C 語言函式庫 | 靜態、動態函式庫 | 使用與編譯 | Library 庫知識**](https://devtechascendancy.com/understanding-c-library-static-dynamic/)
* [**Linux 宏拓展 | offsetof、container_of 宏、鏈表 | 使用與分析**](https://devtechascendancy.com/linux-macro_offsetof_containerof_list/)
:::
## Appendix & FAQ
:::info
:::
###### tags: `C` `Linux Shell`