# 概念
我們編寫的文件,需要經過一定的處理才能轉換成機器上可運行的可執行文件。我們將對C語言的這種處理過程稱為編譯與鏈接。
編譯就是把文本形式源代碼翻譯為機器語言形式的目標文件過程。
鏈接是把目標文件、操作系統的啟動代碼和用到的庫文件進行組織最終形成可執行代碼的過程。
# 實驗
對下面的代碼進行編譯
```c
#include <stdio.h>
#include <stdlib.h>
int e = 10;
int hello();
int main(int argc, char const *argv[]) {
int a[3];
char b[3];
char *c = (char *) malloc(sizeof(char) * 3);
char *d = "hello world";
printf("a: %p\n", a); // 棧
printf("b: %p\n", b); // 棧
printf("c: %p\n", c); // 堆
printf("d: %p\n", d); // 只讀區(常量區)
printf("&d: %p\n", &d); // d 是指針,本身的地址是在棧上
printf("&e: %p\n", &e);
printf("main: %p\n", main); // 代碼區
printf("hello: %p\n", hello);
return hello();
}
int hello(){
return 1;
}
```
## 編譯
`-c`只編譯不鏈接
```
$ gcc test.c -c -o test.o
$ nm test.o
U ___stack_chk_fail
U ___stack_chk_guard
000000000000012c D _e
0000000000000120 T _hello
0000000000000000 T _main
U _malloc
U _printf
```
## 鏈接
找到符號,為找到的符號確定地址
```
$ gcc test.o -o test
$ nm test
U ___stack_chk_fail
U ___stack_chk_guard
0000000100000000 T __mh_execute_header
0000000100001030 D _e
0000000100000f10 T _hello
0000000100000df0 T _main
U _malloc
U _printf
U dyld_stub_binder
```
## 運行
```
$ ./test
a: 0x7fff5405aa9c
b: 0x7fff5405aa85
c: 0x7fb54ac02780
d: 0x10bba5f5e
&d: 0x7fff5405aa70
&e: 0x10bba6030
main: 0x10bba5df0
hello: 0x10bba5f10
```
# 原理
## 鏈接
> 摘錄于《C專家編程》
### 靜態鏈接
Unix 早期編譯時,將需要使用的每個庫函數一份拷貝加入到可執行文件中,也就是**靜態鏈接**。
### 動態鏈接
后來一種更為現代和優越的被稱為動態鏈接的方法逐漸被采用。動態鏈接允許系統提供一個龐大的函數庫集合,可以提供許多有用的服務。程序在運行時尋找它們,而不是把這些函數庫的二進制代碼作為自身可執行文件的一部分。
如果可執行只是包含了文件名,讓載入器在運行時能夠尋找程序所需要的函數庫,那么我們稱之為**動態鏈接**。
### 動態鏈接的優勢
1. 可執行文件體積非常小;
2. 鏈接-編輯階段時間也會縮短;
3. 解耦,把程序和它們使用的函數庫分離(*關鍵字ABI*);
4. 提升計算機整理性能
### 如何提升整理性能
1. 動態鏈接的可執行文件比功能相同的靜態鏈接可執行文件的體積小,能夠節省磁盤空間和虛擬內存,因為函數庫只有在需要時才被映射到進程中。
2. 所有動態鏈接到某個特定函數庫的可執行文件在運行時共享該函數庫的一個單獨拷貝。操作系統內核保證映射到內存中的含數據庫可以被使用它的其他進程共享,提供了更好的I/O和交換空間利用率,節省了物理內存,從而提高了系統的整體性能。如果可執行文件是靜態鏈接的,每個文件都將擁有一份函數庫的拷貝,顯然極為浪費空間(系統調用`mmap`把文件映射到進程的地址空間中)。
## 靜態庫和動態庫
任何人都可以創建靜態或者動態的函數庫,源碼文件不含有`main`函數。
1. 靜態庫(`archive`),通過`ar`來創建和更新,以`.a`作為擴展名。過時了,不推薦使用。動態庫,通過`ld`來創建和更新,以`.so`作為擴展名。
2. 通過`-lthread`選項告訴編譯鏈接到`libthread.so`,`-l{name}`是`-lib{name}.so`的縮寫。
3. 編譯器期望在確定的目錄找到庫,除了系統默認的一些目錄外,可以在鏈接時使用`-Lpathname`和`-Rpathname`來指定。
4. 通過頭文件確認使用的庫函數
5. 始終將`-l`函數庫選項放在編譯命令行的最右邊