# 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 的名稱已經寫在程式碼中了