一、常用編譯選項
-
?基本編譯
gcc [input].c -o [output]
?示例:gcc hello.c -o hello # 將 hello.c 編譯為可執行文件 hello ./hello # 運行程序
-
?分步編譯
- 預處理:
-E
(生成?.i
?文件)gcc -E hello.c -o hello.i
- 編譯為匯編:
-S
(生成?.s
?文件)gcc -S hello.i -o hello.s
- 匯編為目標文件:
-c
(生成?.o
?文件)gcc -c hello.s -o hello.o
- 鏈接為可執行文件:
gcc hello.o -o hello
- 預處理:
-
?調試信息
-g
?選項生成帶調試信息的文件(供 GDB 使用):gcc -g test.c -o test_debug
-
?優化選項
-O1
,?-O2
,?-O3
?表示優化級別(-O2
?最常用):gcc -O2 main.c -o optimized_program
-
?警告選項
-Wall
?啟用所有常見警告-Werror
?將警告視為錯誤
gcc -Wall -Werror strict_code.c -o strict_program
-
?鏈接庫文件
-l
?指定庫名(如數學庫?-lm
)-L
?指定庫文件路徑
gcc calc.c -lm -L/usr/local/lib -o calc
二、示例場景
1. 編譯多文件項目
# 編譯兩個文件并鏈接
gcc main.c utils.c -o app
2. 生成靜態庫
# 1. 編譯為 .o 文件
gcc -c mylib.c -o mylib.o
# 2. 打包為靜態庫
ar rcs libmylib.a mylib.o
# 3. 使用靜態庫
gcc main.c -L. -lmylib -o static_app
3. 生成動態庫(共享庫)
# 1. 編譯為位置無關代碼(-fPIC)
gcc -fPIC -c mylib.c -o mylib.o
# 2. 生成動態庫(-shared)
gcc -shared mylib.o -o libmylib.so
# 3. 使用動態庫(需設置庫路徑)
gcc main.c -L. -lmylib -o dynamic_app
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH # 臨時添加庫路徑
三、C語言是如何編譯的
1. 預處理
類比:準備食材
想象你在做一道復雜的菜,首先你需要把所有的食材準備好,比如切菜、洗菜、調味等。預處理階段就像是這個準備食材的過程。
具體過程
預處理器(cpp
)會處理源代碼文件中的預處理指令,比如?#include
、#define
?和?#ifdef
?等。它會將這些指令替換成實際的代碼或數據。
- ??
#include
??:將指定的頭文件內容插入到當前文件中。 - ??
#define
??:定義宏,替換代碼中的宏定義。 - ??
#ifdef
?/?#ifndef
??:條件編譯,根據條件決定是否包含某段代碼。
示例
#include <stdio.h>
#define PI 3.14159int main() {printf("The value of PI is %f", PI);return 0;
}
預處理后,#include <stdio.h>
?會被替換成?stdio.h
?文件的內容,PI
?會被替換成?3.14159
。
2. 編譯
類比:編寫食譜
現在你已經準備好了食材,接下來你需要編寫一個詳細的食譜,告訴廚師每一步該怎么做。編譯階段就像是這個編寫食譜的過程。
具體過程
編譯器(gcc
)會將預處理后的源代碼轉換成匯編代碼。這個過程會檢查語法錯誤、類型檢查等。
- ??語法檢查??:確保代碼符合C語言的語法規則。
- ??類型檢查??:確保變量和函數的類型使用正確。
示例
預處理后的代碼會被編譯成類似以下的匯編代碼(具體內容取決于編譯器和平臺):
main:push rbpmov rbp, rspsub rsp, 16mov edi, OFFSET FLAT:.LC0call puts@PLTmov eax, 0leaveret
3. 匯編
類比:將食譜翻譯成操作步驟
現在你已經有了詳細的食譜,接下來需要將這些食譜翻譯成具體的操作步驟,讓廚師可以一步步執行。匯編階段就像是這個翻譯過程。
具體過程
匯編器(as
)會將匯編代碼轉換成機器碼,生成目標文件(.o
?文件)。這個過程不會進行任何優化,只是簡單地將匯編指令轉換成機器碼。
示例
上面的匯編代碼會被轉換成機器碼,生成一個目標文件?main.o
。
4. 鏈接
類比:將多個菜譜合并成一本完整的烹飪書
現在你已經有了每道菜的具體操作步驟,接下來需要將這些步驟合并成一本完整的烹飪書,確保所有的步驟和食材都能協調工作。鏈接階段就像是這個合并過程。
具體過程
鏈接器(ld
)會將多個目標文件和庫文件合并成一個可執行文件。這個過程會解析所有的符號引用,確保每個函數和變量的調用都能找到正確的定義。
- ??符號解析??:確保每個函數和變量的調用都能找到正確的定義。
- ??重定位??:將符號的地址修正為最終的內存地址。
假設你有兩個源文件?main.c
?和?utils.c
,編譯后會生成?main.o
?和?utils.o
。鏈接器會將這兩個目標文件合并成一個可執行文件?program
。
總結
- ??預處理??:準備食材,處理?
#include
、#define
?等指令。 - ??編譯??:編寫食譜,將預處理后的代碼轉換成匯編代碼。
- ??匯編??:將食譜翻譯成操作步驟,生成目標文件。
- ??鏈接??:合并多個菜譜,生成最終的可執行文件。
5.函數庫
靜態庫
定義??:靜態庫是將一系列的目標文件(.o
?文件)打包成一個單獨的文件,在編譯鏈接時,鏈接器會把靜態庫中被程序使用的代碼直接復制到最終的可執行文件中。靜態庫的文件擴展名通常是?.a
靜態連接:
在靜態連接中,編譯器和鏈接器在編譯階段就把程序所需要使用的庫代碼(靜態庫)直接合并到最終生成的可執行文件中。
- ??創建靜態庫??:首先要有多個目標文件。假設我們有兩個源文件?
add.c
?和?sub.c
,分別實現加法和減法功能。
// add.c
int add(int a, int b) {return a + b;
}// sub.c
int sub(int a, int b) {return a - b;
}
使用 `gcc` 編譯這兩個源文件生成目標文件:
gcc -c add.c sub.c
然后使用 `ar` 工具創建靜態庫:
ar rcs libmath.a add.o sub.o
這里?ar
?是歸檔工具,r
?表示將目標文件插入到歸檔文件中,c
?表示如果歸檔文件不存在則創建它,s
?表示寫入一個目標文件索引。
?這樣來使用靜態庫
gcc main.c -L. -lmath -o main
-L.
?表示在當前目錄下查找庫文件,-lmath
?表示鏈接名為?libmath.a
?的靜態庫(注意,-l
?后面跟的是庫名去掉?lib
?前綴和?.a
?后綴)。?
優點
- ??獨立性??:可執行文件包含了靜態庫的所有代碼,不依賴外部的庫文件,移植方便。
- ??性能??:由于代碼已經直接嵌入到可執行文件中,運行時不需要額外的加載時間,執行效率較高。
缺點
- ??文件體積大??:每個使用靜態庫的可執行文件都會包含一份庫代碼的副本,會導致可執行文件體積變大。
- ??更新困難??:如果靜態庫有更新,所有使用該庫的可執行文件都需要重新編譯鏈接。
動態庫
定義??:動態庫是在程序運行時才被加載到內存中供程序使用的庫。多個程序可以共享同一個動態庫的內存副本。是C/C++或者其他第三方軟件提供的所有方法的集合,被所有程序以鏈接的方式關聯起來。動態庫的文件擴展名通常是?.so
動態鏈接:
在動態連接中,編譯階段只是記錄程序需要使用哪些動態庫(.so
?文件),然后再需要時加載,并不會把庫代碼復制到可執行文件中。
- 創建動態庫??:同樣使用之前的?
add.c
?和?sub.c
?源文件,先編譯成目標文件:
gcc -c -fPIC add.c sub.c
-fPIC
?表示生成位置無關代碼,這是創建動態庫所必需的。
使用?gcc
?創建動態庫:
gcc -shared -o libmath.so add.o sub.o
-shared
?表示生成共享庫。
??使用動態庫??:
gcc main.c -L. -lmath -o main
編譯命令和靜態庫類似,但在運行可執行文件時,需要確保系統能找到動態庫。可以通過設置?LD_LIBRARY_PATH
?環境變量來指定動態庫的搜索路徑:
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./main
優點
- ??節省內存??:多個程序可以共享同一個動態庫的內存副本,減少了內存的使用。
- ??更新方便??:如果動態庫有更新,只需要替換動態庫文件,不需要重新編譯使用該庫的所有程序。
缺點
- ??依賴問題??:可執行文件依賴于外部的動態庫文件,如果動態庫文件丟失或版本不兼容,程序可能無法正常運行。
- ??加載時間??:程序運行時需要加載動態庫,會有一定的加載時間開銷。
?
注意:linux系統中認為后綴無意義,但不是代表gcc認為后綴無意義,也就是說我們后綴不對的話gcc編譯會出錯的。