文章目錄
- 一、背景知識
- 二、gcc編譯選項
- 1、預處理(進行宏替換)
- 2、編譯(生成匯編)
- 3、匯編(生成機器可識別代碼)
- 4、鏈接(生成可執行文件或庫文件)
- 三、動態鏈接和靜態鏈接
- 四、靜態庫和動態庫
- 1、動靜態庫
- 2、編譯器自舉
- 自舉的基本步驟
- 五、gcc其他常用選項 - 了解即可
一、背景知識
- 預處理(進行宏替換/去注釋/條件編譯/頭文件展開等)
- 編譯(生成匯編)
- 匯編(生成機器可識別代碼)
- 連接(生成可執行文件或庫文件)
二、gcc編譯選項
格式 gcc [選項] 要編譯的文件 [選項] [目標文件]
1、預處理(進行宏替換)
? 預處理功能主要包括: 宏定義,文件包含,條件編譯,去注釋等。
? 預處理指令是以 # 號開頭的代碼行。
? 實例: gcc –E hello.c –o hello.i
? 選項 “-E” ,該選項的作用是讓 gcc 在預處理結束后停止編譯過程。
? 選項 “-o” 是指目標文件, “.i” 文件為已經過預處理的C原始程序。
2、編譯(生成匯編)
? 在這個階段中,gcc 首先要檢查代碼的規范性、是否有語法錯誤等,以確定代碼的實際要做的工作,在檢查無誤后, gcc 把代碼翻譯成匯編語言。
? 用戶可以使用“-S”選項來進行查看,該選項只進行編譯而不進行匯編,生成匯編代碼。
? 實例: gcc –S hello.i –o hello.s
3、匯編(生成機器可識別代碼)
? 匯編階段是把編譯階段生成的“.s”文件轉成目標文件
? 讀者在此可使用選項“-c”就可看到匯編代碼已轉化為“.o”的二進制目標代碼了
? 實例: gcc –c hello.s –o hello.o
4、鏈接(生成可執行文件或庫文件)
? 在成功編譯之后,就進入了鏈接階段。
? 實例: gcc hello.o –o hello
三、動態鏈接和靜態鏈接
在我們的實際開發中,不可能將所有代碼放在一個源文件中,所以會出現多個源文件,而且多個源文件之間不是獨立的,而會存在多種依賴關系,如一個源文件可能要調用另一個源文件中定義的函數,但是每個源文件都是獨立編譯的,即每個*.c文件會形成一個*.o文件,為了滿足前面說的依賴關系,則需要將這些源文件產生的目標文件進行鏈接,從而形成一個可以執行的程序。這個鏈接的過程就是靜態鏈接。
靜態鏈接的缺點很明顯:
? 浪費空間:因為每個可執行程序中對所有需要的目標文件都要有一份副本,所以如果多個程序對同一個目標文件都有依賴,如多個程序中都調用了printf()函數,則這多個程序中都含有printf.o,所以同一個目標文件都在內存存在多個副本;
? 更新比較困難:因為每當庫函數的代碼修改了,這個時候就需要重新進行編譯鏈接形成可執行程序。但是靜態鏈接的優點就是,在可執行程序中已經具備了所有執行程序所需要的任何東西,在執行的時候運行速度快。
動態鏈接的出現解決了靜態鏈接中提到問題。動態鏈接的基本思想是把程序按照模塊拆分成各個相對獨立部分,在程序運行時才將它們鏈接在一起形成一個完整的程序,而不是像靜態鏈接一樣把所有程序模塊都鏈接成一個單獨的可執行文件。
動態鏈接其實遠比靜態鏈接要常用得多。比如我們查看下 hello 這個可執行程序依賴的動態庫,會發現它就用到了一個c動態鏈接庫:
$ ldd hellolinux-vdso.so.1 => (0x00007fffeb1ab000) libc.so.6 => /lib64/libc.so.6 (0x00007ff776af5000) /lib64/ld-linux-x86-64.so.2 (0x00007ff776ec3000)
# ldd命令用于打印程序或者庫文件所依賴的共享庫列表。
在這 里涉及到一個重要的概念: 庫
? 我們的C程序中,并沒有定義“printf”的函數實現,且在預編譯中包含的“stdio.h”中也只有該函數的聲明,而沒有定義函數的實現,那么,是在哪 里實“printf”函數的呢?
? 最后的答案是:系統把這些函數實現都被做到名為 libc.so.6 的庫文件中去了,在沒有特別指定時,gcc 會到系統默認的搜索路徑“/usr/lib”下進行查找,也就是鏈接到libc.so.6 庫函數中去,這樣就能實現函數“printf”了,而這也就是鏈接的作用
四、靜態庫和動態庫
1、動靜態庫
? 靜態庫是指編譯鏈接時,把庫文件的代碼全部加入到可執行文件中,因此生成的文件比較大,但在運行時也就不再需要庫文件了。其后綴名一般為“.a”
? 動態庫與之相反,在編譯鏈接時并沒有把庫文件的代碼加入到可執行文件中,而是在程序執行時由運行時鏈接文件加載庫,這樣可以節省系統的開銷。動態庫一般后綴名為“.so”,如前面所述的 libc.so.6 就是動態庫。gcc 在編譯時默認使用動態庫。完成了鏈接之后,gcc 就可以生成可執行文件,如下所示。 gcc hello.o –o hello
? gcc默認生成的二進制程序,是動態鏈接的,這點可以通過 file 命令驗證。
📌 注意:
? Linux下,動態庫XXX.so, 靜態庫XXX.a
? Windows下,動態庫XXX.dll, 靜態庫XXX.lib
一般我們的云服務器,C/C++的靜態庫并沒有安裝,可以采用如下方法安裝
# Centos
yum install glibc-static libstdc++-static -y
#ubuntu
略(apt)
2、編譯器自舉
編譯器自舉(Bootstrapping)是指通過現有的編譯工具來構建新的編譯器的過程,尤其是指用目標語言本身編寫的新編譯器。這個過程的核心思想在于“自我宿主”——即一個高級語言程序可以用來編譯其自身的源代碼。
自舉的基本步驟
-
初始階段:從零開始創建一個小規模、功能有限的編譯器版本A0。它可以將一小部分核心語法結構翻譯為目標機器碼或其他中間表示形式。
-
迭代改進:利用現有編譯器(可能是手工編寫的簡單解釋器或者是另一種成熟語言提供的編譯系統),把更復雜的特性逐步加入到新編譯器中,并能夠處理更多的語言特性和優化技術。此時會產生一個新的編譯器版本A1。
-
循環增強:隨著每一輪迭代,編譯器的功能越來越強大,直到它能完全理解和轉換整個預期的目標語言為止。最終得到的就是全功能版的編譯器An,而該編譯器已經可以用自身生成的二進制文件來進行后續更新和發展了。
-
驗證一致性:為了保證質量,在每次升級過程中都需要進行嚴格的測試,確保每個新版本都能正確解析并編譯舊版本所支持的所有合法輸入。
-
獨立運行:一旦成功完成所有輪次的迭代并且經過充分調試之后,就可以移除對原始輔助編譯環境的需求,使得最終產物成為一個獨立運作的產品。
實際案例
許多現代編程語言都是采用這種方式發展的,例如GCC (GNU Compiler Collection) 的開發就是基于C/C++本身的;還有像Python這樣的腳本語言也經歷了類似的發展路徑,其中CPython實現了Python標準庫以及解釋器的主要組成部分。
五、gcc其他常用選項 - 了解即可
? -E 只激活預處理,這個不生成文件,你需要把它重定向到一個輸出文件 里面
? -S 編譯到匯編語言不進行匯編和鏈接
? -c 編譯到目標代碼
? -o 文件輸出到 文件
? -static 此選項對生成的文件采用靜態鏈接
? -g 生成調試信息。GNU 調試器可利用該信息。
? -shared 此選項將盡量使用動態庫,所以生成文件比較小,但是需要系統由動態庫.
? -O0
? -O1
? -O2
? -O3 編譯器的優化選項的4個級別,-O0表示沒有優化,-O1為缺省值,-O3優化級別最高
? -w 不生成任何警告信息。
? -Wall 生成所有警告信息。