# 編譯、連結 library
## GCC預設的參數有什麼?代表什麼意思
```bash
gcc --help // 可以看到可用的參數與其解釋
```
### **gnu編譯過程**

### 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啟動

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

使用static library

```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*
```

```bash!
// 找可以支援的CXXABI
strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6|grep CXXABI
```

:::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

```bash!
gcc -H -c your_source.c
```
- -H => 顯示在編譯過程中每個header file的搜索路徑

## 如果編譯過程中出現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. 系統整合:
安裝的套件其文件放置在系統預定的目錄中,並且可以被其他系統工具發現。