---
# System prepended metadata

title: 編譯、連結 library
tags: [Library]

---

# 編譯、連結 library

## GCC預設的參數有什麼？代表什麼意思
```bash
gcc --help // 可以看到可用的參數與其解釋
```
    
### **gnu編譯過程**
![image](https://hackmd.io/_uploads/HJEjncrVa.png)

### 1. 預處理
將 include 、 macro 擴展成真正的內容，經過這一步驟後，檔案會變大許多。

使用 GCC 進行預處理的方式如下:
```bash
gcc -E -I./src main.c -o main.i
```
    
- -E 會讓 gcc 僅對 c 程式做預處理
- -I 用來指定自定義標頭檔的優先搜尋目錄，若你定義的標頭檔與程式碼同目錄，就可以忽略該參數。
- -o 指定輸出檔案的檔名與格式

:::info
***巨集(Macro)***
在編譯指令中宣告或是在程式碼中使用#define宣告
``` bash
// define
gcc -D DEBUG -o main main.c
// cancel defination
gcc -U DEBUG -o main main.c
```
    
可以使用巨集宣告定值、進行有條件編譯甚至是撰寫擬函式
```c
// 宣告定值
#define SIZE 10
// 撰寫擬函式
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// 有條件編譯
#ifndef SOMETHING_H
#define SOMETHING_H

/* Declare some data types and public functions. */

#endif /* SOMETHING_H */
// 有條件編譯
#ifdef DEBUG
    fprintf(stderr, "Some message\n");
#endif
```
:::
    
    
### 2. 編譯
將預處理過的程式碼編譯成組合語言，而組合語言又會因為不同的處理器架構出現差異。
    
使用 GCC 進行編譯的方法:
```bash
gcc -S -I./src main.c -o main.s
```
- -S 僅輸出 C 程式對應的組合語言
:::info
***交叉編譯***
        假設要編譯出能在arm架構上執行的程式碼，通常因為效能和時間成本的關係不會直接在arm平台上編譯，而是可以先在x86先驗證程式碼(當然這邊是先編譯x86版本)，接著再編譯出ARM(aarch64)的檔案部署到機器上整合測試
        使用Toolchain內提供的交叉編譯器以及資料庫(Library)，Toolchain提供了個階段的交叉編譯工具。無論你使用gcc或是Make或是CMaker，只要設定好路徑跟環境，整個交叉編譯的程序就可以直接在Host啟動
      
![image](https://hackmd.io/_uploads/HkWBgJHVa.png)
reference: https://allenshaing.wordpress.com/2021/08/21/cross-compiler-101/

:::
    
### 3. 組譯
經過編譯後，我們會得到組合語言檔案，也就是 *.s 檔案。不過，若要讓電腦能夠執行我們的程式碼，我們仍需要將其轉換成機器碼。
```bash
gcc -c main.s -o main.o
```
- -c 僅編譯 C 程式，但不連結，即輸出對應的目標碼
    
### 4. 連結
連結會將多個目標文件或是 library 連結成可執行檔。
```bash
gcc -o main main.o
```
如果有多個機器碼最後會被連結成一個可執行檔案，也可以這麼做:
```bash
gcc -o main a.o b.o c.o
```
這樣一來，如果開發者更動了其中一個檔案，我們只需對有更動的檔案進行編譯再連結起來。可以大幅節省編譯的時間。

:::info
**static linking**
靜態函式庫（static library）就是由一些物件檔案（object files）所構成的封裝檔，通常其檔案名稱都會以 lib 開頭，而副檔名則為 .a。

使用靜態連結函式庫的好處就是所有的程式都包裝在執行檔中，不會因為缺少函式庫的檔案而不能執行，不過缺點就是這樣的執行檔大小會比較大，而如果函式庫有更新的話，整個執行檔也要跟著重新編譯。
```bash!
gcc -o main main.c sum.c

// static linking
gcc -c -o sum.o sum.c
ar -rcs libsum.a sum.o
// linking 第一種寫法
gcc main.c -L. -lsum -o main_static
// linking 第二種寫法
gcc -o main_static main.c libsum.a 
```
:::
:::success
**ar指令參數**
- r => 把參數中所有的檔案插入 library 檔案中，若該檔案已存在於 library 中，覆蓋掉原本的內容
- c => 新增一個 library 檔案
- s => 在 library 中寫入 object file 的 index
:::
:::info
**shared library**
共享函式庫（shared library）是在程式實際開始執行時，才會被載入的函式庫，執行檔本身與共享函式庫是分離的，這樣可以讓執行檔的大小比較小，而且未來共享函式庫在更新之後，執行檔也不需要重新編譯，而缺點則是執行檔在執行時就會需要共享函式庫的檔案，如果缺少了共享函式庫的檔案，就會無法執行。
```bash!
gcc -shared -o libsum.so sum.o
gcc -o main_dynamic main.c libsum.so
LD_LIBRARY_PATH= . ./main_dynamic
// LD_LIBRARY_PATH代表環境變數的設置
// '.'代表目前所在位置
```
**底下以openssl為例子：**
使用shared library
![image](https://hackmd.io/_uploads/ryiEhoSVp.png)
使用static library
![image](https://hackmd.io/_uploads/SkQtToHE6.png)
```bash!
du -sh openssl 
// 1.2M    openssl => shared library
// 7.5M    openssl => static library
```

::: 
    
:::info
***GNU Debugger***
使用GDB進行除錯
```bash
gcc -Wall -g main main.c
```
- -Wall: 顯示所有警告信息
- -g: 使gcc要盡可能告知資訊給GDB幫助除錯
:::
## 理解C++有不同標準版本：C++11, C++14, C++17, C++20。
- GCC如何指定不同版本？(不同版本可能在參數上不一樣)
```bash
gcc -std=c11 -o main main.c
```
- 使用library前先閱讀最低版本需求，否則可能編譯失敗
## GCC有哪些效能優化的參數，如果編譯指令有多個參數，會依照哪一個為準？
:::warning
如果使用多個級別的優化選項，會以最後一個為準。
:::

### **程式最佳化**
**-O1** 
    => 編譯器會在不做任何將編譯時間拉很長的優化的前提下，降低程式碼的大小與執行時間

          -fdefer-pop
          -fmerge-constants
          -fthread-jumps
          -fif-conversion
          -fif-conversion2
          -fdelayed-branch
          -fguess-branch-probability
          -fcprop-registers
**-O2**
    => 不會做loop unrolling 或是 function inlining，並且相對於O1而言，增加了編譯的時間與生成出的組合語言的效能。
    
        
          -fforce-mem 
          -foptimize-sibling-calls 
          -fstrength-reduce 
          -fcse-follow-jumps  -fcse-skip-blocks 
          -frerun-cse-after-loop  -frerun-loop-opt 
          -fgcse  -fgcse-lm  -fgcse-sm  -fgcse-las 
          -fdelete-null-pointer-checks 
          -fexpensive-optimizations 
          -fregmove 
          -fschedule-insns  -fschedule-insns2 
          -fsched-interblock  -fsched-spec 
          -fcaller-saves 
          -fpeephole2 
          -freorder-blocks  -freorder-functions 
          -fstrict-aliasing 
          -funit-at-a-time 
          -falign-functions  -falign-jumps 
          -falign-loops  -falign-labels 
          -fcrossjumping
:::info
***loop unrolling: 一種犧牲程式的大小來加快程式執行速度的最佳化方法***
- 優點
        - branch prediction的失敗次數減少。
        - 如果循環結構內語句沒有data dependency，增加了平行執行的機會。
- 缺點
        - 程式碼膨脹。
        - 循環結構內若包含遞迴可能會降低循環展開的效益。
```c
    for (i = 1; i <= 60; i++) 
       a[i] = a[i] * b + c;
```
```c
    for (i = 1; i <= 58; i+=3)
    {
        a[i] = a[i] * b + c;
        a[i+1] = a[i+1] * b + c;
        a[i+2] = a[i+2] * b + c;
    }
```
:::

:::info
***inline function***
一般而言，當我們撰寫函數，並呼叫使用，電腦的機器語言指令會紀錄目前工作階段的記憶體位址，然後跳至函數的記憶體位置處理完程序後，並回到原先的位址上，而這樣來回會造成時間上的額外負擔。若開啟function inlining的功能，編譯時便會把函數中的程式直接展開。
```c
    int pred(int x)
    {
        if (x == 0)
            return 0;
        else
            return x - 1;
    }
```
Before inlining:
```c
    int func(int y) 
    {
        return pred(y) + pred(0) + pred(y+1);
    }
```
After inlining:
```c
    int func(int y) 
    {
        int tmp;
        if (y   == 0) tmp  = 0; else tmp  = y - 1;       /* (1) */
        if (0   == 0) tmp += 0; else tmp += 0 - 1;       /* (2) */
        if (y+1 == 0) tmp += 0; else tmp += (y + 1) - 1; /* (3) */
        return tmp;
    }   
```
:::
    
**-O3**
=> 相對於O2而言，增加了以下優化的選項
        
        -finline-functions
        -fweb
        -frename-registesr
        -funswitch-loops
reference:
    [1]https://gcc.gnu.org/onlinedocs/gcc-3.4.6/gcc/Optimize-Options.html#Optimize-Options
    [2]https://en.wikipedia.org/wiki/Loop_unrolling
    [3]https://en.wikipedia.org/wiki/Inline_expansion
## 什麼是glibc？他和musl libc有什麼差別？
glibc為GNU C Library，glibc於設計上有諸多不足之處，例如全靜態連結因NSS機制而有狀況、內部機制甚多而難以維護/移植、組語太多、pthread實做有bug......，因此Rich Felker重新設計出一個新的C Library - musl。

musl 被設計為更輕量級和高效的函式庫，特別適用於資源受限的環境，如嵌入式系統。musl傾向於嚴格遵循POSIX標準，這可能使得使用musl編譯的程式更容易在不同系統上實現二進制文件的可移植性。並且使用shared library的時候不支援lazy binding。
:::info
**二進位可移植性**
將保證任何程式在符合標準的給定硬體平台上一旦編譯通過，可以在符合同樣標準的任何其他硬體平台上以編譯後的形式執行。

**lazy binding**
在library call 真正被呼叫時再去載入。
:::
reference: https://wiki.musl-libc.org/functional-differences-from-glibc.html#:~:text=glibc%20allows%20the%20usage%20of,in%20the%20regular%20symbol%20tables.
## 什麼CXXABI？他與GCC及glibc的版本有什麼關係？
- Application Binary Interface(ABI)
    當c語言程式碼編譯成組合語言後，透過ABI將組合語言轉換成機器語言
- CXXABI為c++的ABI，不同版本的GNU C Library支援不同的CXXABI
- gcc -> glibc(GNU C Library) -> ABI
```bash!
// 先找尋目前正在使用的GNU C Library版本
ll /usr/lib/x86_64-linux-gnu/libstdc*
```
![截圖 2023-11-20 上午10.34.09](https://hackmd.io/_uploads/HkiarHuNT.png)
```bash!
// 找可以支援的CXXABI
strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6|grep CXXABI
```
![image](https://hackmd.io/_uploads/ByCkIBd4p.png)
:::warning
若出現 Error: /lib/libstdc++.so.6: version 'CXXABI_1.3.8' not found 類似的錯誤碼，可以考慮更換GNU C Library的版本，並使用ln指令更換新的連結
:::
reference: https://www.xdull.cn/cxxabi.html
## 一個Linux作業系統裡面可能有好幾個stdio.h，我該如何確認GCC會引用哪個stdio.h
![image](https://hackmd.io/_uploads/ByFXx8uNT.png)
```bash!
gcc -H -c your_source.c
```
- -H => 顯示在編譯過程中每個header file的搜索路徑

![image](https://hackmd.io/_uploads/Syk8gU_ET.png)
## 如果編譯過程中出現undefined reference該怎麼辦
1. 確定引用函數時的格式是否錯誤，或是該函數屬於的標頭檔是否有引入
2. 確定library是否有被正確連接上並檢查版本相容性
- 如果你在使用外部的library，確保被正確連接到編譯過程中，需要使用 -l 選項指定庫的名稱，並且 -L 選項指定庫的搜索路徑。
```bash!
// 這裡 -lmylibrary 指定了要使用的library，而 -L/path/to/library 指定了library的路徑。
gcc -o myprogram myprogram.c -lmylibrary -L/path/to/library
```
3. 確定include file的路徑正確
- 如果你在標頭檔中聲明了某個函數或變數，確保其路徑正確。你可以使用 -I 選項指定標頭檔的路徑。
```bash!
gcc -o myprogram myprogram.c -I/path/to/headers
```
4. 檢查編譯器和library的相容性：
- 確保你使用的編譯器和library是相容的，有時不同版本的編譯器或library之間可能存在不同的ABI。
## 如果我確定已經安裝某個library，但仍然編譯失敗，出現undefined reference，有哪些方法可以讓GCC找到我安裝的library
1. 檢查環境變數 LD_LIBRARY_PATH：
確保環境變數 LD_LIBRARY_PATH 包含了庫文件的搜索路徑。這個環境變數告訴運行時連接器（runtime linker）在哪裡找shared library。
```bash!
export LD_LIBRARY_PATH=/path/to/library:$LD_LIBRARY_PATH

gcc -o myprogram myprogram.c -lmylibrary -L/path/to/library -Wl,-rpath=/path/to/library
```
## 如果編譯過程中出現no such file "ooxx.h"該怎麼辦
1. 確認標頭檔的位置與權限：
2. 指定正確的標頭檔路徑
- 確定include file的路徑正確
    - 如果你在標頭檔中聲明了某個函數或變數，確保其路徑正確。你可以使用 -I 選項指定標頭檔的路徑。
```bash!
gcc -o myprogram myprogram.c -I /path/to/headers
```
## 使用apt或yum安裝library和我自己下載原始碼編譯有什麼不同
1. 安裝/卸載：
    通過一條命令安裝套件及其相依項目，省去額外的步驟，包括解壓縮、配置、編譯、安裝與一些相依性問題。在卸載時也解決相依性的問題同時保持系統的整潔。
2. 版本管理：
    不僅可以解決不同套件之間版本衝突的問題，通過一條指令可以將套件升級或退回到特定版本。
3. 系統整合：
    安裝的套件其文件放置在系統預定的目錄中，並且可以被其他系統工具發現。