GCC編譯過程概述

來源:公衆號【很酷的程序員/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將源文件編譯成最終的可執行文件,包含以下幾個步驟:

  1. 預處理:將源文件處理爲.ii/.i,處理各種預處理指令,如#include#ifdef#if等等,同時也會清除註釋;
  2. 編譯:將.ii/.i處理爲.S/.asm,即機器語言的彙編文件;
  3. 彙編:將.asm/.S處理爲.o,把彙編文件變成機器碼;
  4. 鏈接:將各種依賴的靜態/動態庫文件、.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負責鏈接。

cc1collect2位於目錄/usr/lib/gcc/x86_64-linux-gnu/9/下。
在不同的系統上可能會較大差異,尤其是鏈接的時候,很酷的童鞋們可以自行探索一番哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章