---
# System prepended metadata

title: 連結 Library
tags: [C Programming]

---

# GCC: Linking 

## 編譯的步驟

- **Preprocess**
    - 處理、展開所有 Preprocessor (`#` 開頭的語法)
    - 包含:
        - `include`: 把要求的檔案內容，展開 (插入) 到目前原始碼中
        - `define`: 定義巨集
        - `pragma`: 使用 compiler directives，指引編譯器執行一些額外的動作
        - ...
- **Compile**
    - 把處理好的原始碼，轉換成 **assembly code**
- **Assemble**
    - 把 **assembly code** 組譯成 **machine code** (binary)
    - 組譯的結果是 **object file**，一個原始碼檔案會產生一個 object file
        - 一個完整的程式通常不會只寫在一個檔案，所以一個原始碼檔案中的程式碼通常都是不完整的
        - 所以 Object file 中的 binary code 可能只包含整個程式的一部分
- **Link**
    - 連結多個 Object file 或其他 binary (例如 library)
        - 把多個不完整的 binary 組合成具有完整功能的 binary
    - 通常是 **程式的執行檔**
    - 或是 **函式庫的 binary**
        - **Static Library** (*.a*): 被其他程式使用時，只支援 compile-time linking
        - **Shared Library** (*.so*): 被其他程式使用時，會在 run-time 才 linking；因為需要 OS 另外載入 library ，所以要讓 OS 知道 linking 的檔案位置 (執行程式前，要設定環境變數，或是把 library 檔案放到系統的函式庫路徑)

![](https://i.imgur.com/JMOk7Mz.png)

### 儲存每個階段的 Output

- 預設情況下，`gcc` 會一次做完所有步驟，中間產出的結果不會被儲存在檔案中
- 如果要儲存每個步驟的 output 到檔案中，可以在編譯的指令中加入對應的 option

:::info
**Preprocess only**: `-E`
- 只展開 preprocessor，所以輸出結果還是 C 語言
- 真正 **被編譯的原始碼**
- 輸出到 stdout
    ```
    gcc main.c -E -o 
    ```
- 儲存成檔案 (常使用附檔名 *.i*，但不是硬性規定)
    ```
    gcc main.c -E -o main.i
    ```
:::

:::success
**Compile only**: -S
- 只進行編譯 (C $\Rightarrow$ Assembly)，輸出結果是 **組合語言**
- 會自動儲存成檔名相同，副檔名是 *.s* 的檔案
    ```
    gcc main.c -S
    ```
:::

:::warning
**Compile and assemble**: -c
- 編譯並組譯，但是不 link，產出 binary (object file)
- 會自動儲存成檔名相同，副檔名是 *.o* 的檔案
    ```
    gcc main.c -c
    ```
:::

## Object Files

- 一個原始碼檔案 (*.c*, *.cpp*, *.cc*) 可以被編譯成一個 objet file
    - 一個原始碼檔案中的程式碼通常不是完整的，所以 objet file 可能是內容不完整的 machine code
    - Object file 的編碼和可執行檔相同，但內容可能不完全所以不能直接執行
- 可以 link 多個 object file，得到內容完整的二進位檔案

### 建立 Object File

- 在編譯的指令中加上 `-c`
    ```
    gcc utils.c -c
    ```
- 輸出的檔案的副檔名是 *.o*

## Library

- **Object file** 通常是 **自己寫的原始碼** 編譯後的 binary
    - 一個原始碼檔案，對應一個 object file
    - 是編譯過程的中間產物
- 如果使用別人發布的 library，或是要把自己的專案封裝成 library 給別人使用
    - 通常會把 **整包原始碼** 編譯成一個 **binary file**
- 把所有 **檔案 (source code, binary) compile 成一個 binary file** (Library)
    - **只有此檔案會被發布** (object file 只是編譯過程的中間產物，不被其他人使用)
    - Library 的使用者編譯自己的程式時，只要 link 一個檔案
- 通常還會把所有給使用者的 **API**，**放到同一個 header file** 裡面
    - 使用者只需要 include 一個檔案
    - 可以達到 OOP 中封裝的效果
        - 只要不提供使用者對應的 header，使用者就不能呼叫該 function
- Library 的檔案名稱通常 **開頭是 *lib***
    - 例如: Standard C library 是 *libc*，math 是 *libm*

### Static Library

- 只支援 **static linking** (compile time linking) 的 library
- 副檔名通常是 ***.a***
- 使用 static library 時
    - 和 library 的 **link 發生在 compile-time**
    - 最終產出的檔案就包含 library 的所有 binary code
- 執行時不會再用到 library 檔案
    - 執行檔中就包含 library

> 類似 linkage editor 的效果

### Shared Library

- 支援 **run-time linking** 的 library
- 副檔名通常是 ***.so*** (shared object)
- 使用 shared library 時
    - 編譯時仍需要 library 檔案
    - 但是 **不會** link library 和其他程式碼
- **啟動程式時**
    - **程式執行檔和 library 被載入** 到記憶體中
    - 因為 library 也要被載入，所以作業系統必須知道 library 的位置
        - 啟動程式前，**設定環境變數 `LD_LIBRARY_PATH`** 為 library 所在的目錄
    - 或是把 library 存在系統的函式庫目錄中
        - 相關設定在 */etc/ld.so.conf*
        - 通常包含 */lib*, */usr/lib* 等目錄
        - 用 `sudo ldconfig -p` 可以列出所有 library，同時可以看到他們所在的目錄

> 類似 linking loader 的效果

### Dynamically Loaded Library

- Library 的檔案本身和 shared library 相同
- 但 library 是在 **程式執行時才被載入**
    - 程式執行到需要 library 的地方，才會 load 並 link 所需的 library
    - 需要修改程式的原始碼，加上動態載入 library 的 function call
- 和 shared library 一樣，library 的檔案要放在 */etc/ld.so.conf* 列出的目錄中

> 類似 Windows 中的 DLL

## GCC 中的 Link

- GCC 指令中，只要把所有要 compile、link 的檔案加入指令參數，就可以完成所有工作
    - GCC 會自動判斷哪些是原始碼 (要先編譯才能 link)，哪些是 binary (可以直接 link)
        ```make
        # link object file
        gcc main.c utils.o -o main
        gcc main.o utils.o -o main

        # link static lib
        gcc main.c libutils.a -o main
        gcc main.o libutils.a -o main

        # link shared lib
        gcc main.c libutils.so -o main
        gcc main.o libutils.so -o main
        ```
- 這種方法 **只適合用在編譯同個目錄下的檔案**
    - 如果所有檔案都在 **同個目錄**，可以直接使用相對路徑 (**檔名即路徑**)
    - 如果檔案在 **其他目錄**，其路徑 (不管是絕對還是相對) 都很難維護
        - 無法保證所有開發人員擁有相同的檔案結構
        - 專案的路徑可能不是固定
        - 缺乏彈性
- 而且 library 和對應的 header file，通常不會和原始碼檔案放在一起
    - 通常會在 **系統中的特定目錄**
        - Library: */lib*, */usr/lib* ...
        - Header: */usr/include* ...
    - 或是 **專案中獨立於原始碼之外的特定目錄**
        - Library: 底下的 lib 目錄 (e.g., *./lib*)
        - Library: 底下的 include 目錄 (e.g., *./include*)
    - 不好表示它們的路徑

### `-l` Option

- GCC 有 **自動搜尋 library 檔案** 進行 link 的選項
    - 指令中加入 `-l<lib_name>`，讓編譯器自動搜尋並連結 library
    - 使用 `-l` 時，只需要加上 library 的名稱，***不***需要 ***lib*** 和 **副檔名**
        - link *libm* $\Rightarrow$ `-lm`
        - link *libpng* $\Rightarrow$ `-lpng`
- GCC 會自動搜尋符合條件的 *.so* 檔和 *.a* 檔，並且優先使用 *.so*
    - `-lm` $\Rightarrow$ 搜尋 <i>libm</i>*.so* 或 *libm.a*
    - `-lpng` $\Rightarrow$ 搜尋 <i>libpng</i>*.so* 或 *libpng.a*
- 如果要手動指定特定的檔案，可以在 `-l` 中使用完整路徑
- 當 library 不在系統函式庫目錄中，要在編譯時 **用 `-L` 另外設定 search path**

## Search Path

- **Search Path** 是編譯器在搜尋 library、header 檔案時，會查看的目錄
    - 包含 **include path** 和 **library path**
    - 可以修改設定檔、環境變數，讓編譯器在多個 path 中搜尋

:::success
**Include Path**
- 編譯器 **展開 `#include`** 時，**搜尋 header file 的路徑**
- 預設包含: **目前目錄**、系統的 include 目錄
- 查看目前預設的 include path 可以執行以下指令
    ```
    gcc /dev/null -xc -E -v
    ```
:::

:::info
**Library Path**
- 編譯器 **進行 link** 時，**搜尋 library 的路徑**
- 預設 **只包含系統的 library 目錄**，***不*** 包含目前目錄
:::

### 增加 Include Path (-I)

- 使用 `-I<path>` 可以增加本次編譯使用的 include path
- 舉例來說，要引入的 header 在 *include/* 中:
    - 檔案結構
        ```
        .
        ├── include/
        │   └── utils.h
        └── src/
            └── utils.c
        ```
        - *utils.c* 引入 *utils.h*
    - 在 *src/* 中編譯 utils.c
        ```
        gcc utils.c -I../include -c
        ```

### 增加 Library Path (-L)

- 使用 `-L<path>` 可以增加本次編譯使用的 library path
- 舉例來說，要 link 目錄 *lib/* 中的 `libutils.so`
    - 檔案結構如下
        ```
        .
        ├── include/
        │   └── utils.h
        ├── lib/
        │   └── libutils.so
        └── src/
            └── main.c
        ```
    - 在 *src/* 中編譯 *main.c*
        ```
        gcc main.c -I../include -L../lib -lutils -o main
        ```
        - `-L../lib`:  在 *<font>.</font>./lib* 中搜尋 library
        - `-lutils`: 連結 *libutils* 
        - `-I../include`: 在 *<font>.</font>./include* 中搜尋 header file
        - 指令中沒有指定要引入哪個 header 的選項，是因為 header 的名稱已經寫在程式碼中了
