來源:公衆號【很酷的程序員/RealCoolEngineer】
瞭解GCC如何將源文件編譯爲最終文件,開發者能夠更加清楚如果進行調試、優化,對於理解大型程序的構建過程,使用類似CMake、Bazel等構建工具都是很有幫助的。
本文的目的在於介紹GCC的大體編譯過程,使用簡單的代碼展示編譯的完整過程,每一步的細節暫不展開。
一 GCC簡介
GCC是Linux平臺上最爲常用的編譯工具,全稱GNU Compiler Collection
,即GNU編譯器套件
,它包含多種語言的編譯器及對應的庫。
1 gcc命令
本文以C語言源文件編譯進行介紹,使用的編譯器工具爲gcc
。
gcc
用法:
Usage: gcc [options] file...
常用參數:
參數 | 含義 |
---|---|
-o | 指定輸出文件路徑 |
-E | 只對源文件進行預處理,輸出.i文件 |
-S | 對源文件進行預處理、編譯,輸出.s文件 |
-c | 對源文件進行預處理、編譯、彙編,輸出.o文件 |
-I | 大寫的i,包含頭文件路徑,如 gcc -Ireal/cool/include/ |
-L | 大寫的l,鏈接庫文件路徑,如 gcc -Lreal/cool/lib/ |
-l | 小寫的l,鏈接庫文件,如鏈接librealcool.a:gcc -lrealcool |
-fPIC | 生成位置無關代碼(position-independent code) |
-Wall | 對代碼所有可能有問題的地方發出警告 |
-g | 在目標文件中嵌入調試信息,便於gdb調試 |
二 gcc編譯流程
考慮最簡單的情況,編譯一個簡單的可執行文件,假設源文件demo.c
的內容如下:
// @Author: Farmer Li, 公衆號: 很酷的程序員/RealCoolEngineer
// @Date: 2021-06-15
#include <stdio.h>
int func(int a, int b, int c) { return a * b + c; }
int main(void) {
int a = 10;
int b = 100;
int c = 24;
printf("%d * %d + %d = %d, Cool!\n", a, b, c, func(a, b, c));
return 0;
}
gcc
將源文件編譯成最終的可執行文件,包含以下幾個步驟:
-
預處理:將源文件處理爲.ii/.i,處理各種預處理指令,如
#include
、#ifdef
、#if
等等,同時也會清除註釋; -
編譯:將
.ii/.i
處理爲.S/.asm
,即機器語言的彙編文件; -
彙編:將
.asm/.S
處理爲.o
,把彙編文件變成機器碼; -
鏈接:將各種依賴的靜態/動態庫文件、
.o
文件、啓動文件鏈接成最終的可執行文件或者共享庫文件。
1 預處理
使用gcc -E
僅進行預處理操作:
gcc -E demo.c –o demo.i
得到的.i
文件還是文本文件,查看其內容可以看到#include <stdio.h>
和註釋被處理了,其餘源碼在demo.i
的最後面。
2 編譯
通過gcc -S
將源文件或者.i
文件編譯成.s
文件,可以使用-o
指定目標名稱:
gcc -S demo.c # 生成同名.s文件
# 或者
gcc -S demo.i # 生成同名.s文件
查看demo.s
文件可以看到其中有很多的彙編指令。開發者也可以自己編寫彙編源代碼,在編譯構建完整項目的時候作爲源碼直接使用。
3 彙編
使用gcc -c
將源文件或者.s
文件處理成機器碼,同樣也可以使用-o
指定目標名稱:
gcc -c demo.c # 生成同名.o文件
# 或者
gcc -c demo.s # 生成同名.o文件
在這一步,其實背後是使用匯編器as
將.s
文件處理成.o
文件的,所以下面的命令生成的.o
文件和前面兩條命令完全一樣:
as -o demo.o demo.s
4 鏈接
使用gcc
命令不帶任何參數,指定源文件(.i/.s/.o/.c)則就會按需完成前面的3個步驟,並進行最終的鏈接,最簡單的情況只需要執行命令:
gcc demo.c
就會默認將源文件編譯爲a.out
,當然也可以使用-o
指定輸出文件的名稱:
gcc demo.c -o demo
然後就可以在命令行中執行:
➜ # ./demo
10 * 100 + 24 = 1024, Cool!
-L
和-l
兩個參數是給鏈接器使用的,用於指定目標文件,假如目標程序使用了/usr/lib/libm.a
就可以使用命令:
gcc demo.c -L/usr/lib -lm
使用-l
參數的時候,可省略庫文件開頭的lib
和後綴。
5 查看gcc編譯的過程
可以使用-v
參數查看gcc
的詳細編譯過程:
gcc demo.c -v
以前面的源文件編譯可執行文件爲例,下面是在ubuntu 20.04
下面看到的輸出(刪除了大部分參數內容,只留下框架主體):
cc1 -quiet -v demo.c -Wformat -o /tmp/ccslRPiN.s
as -v --64 -o /tmp/ccaoMQtO.o /tmp/ccslRPiN.s
collect2 -L/usr/lib /tmp/ccaoMQtO.o -lgcc /usr/lib/x86_64-linux-gnu/crtn.o
可以看到,cc1
做了預處理和編譯的工作,彙編由as
完成,而collect2
負責鏈接。
cc1
和collect2
位於目錄/usr/lib/gcc/x86_64-linux-gnu/9/
下。
在不同的系統上可能會較大差異,尤其是鏈接的時候,很酷的童鞋們可以自行探索一番哦。