c++編譯鏈接過程以及相關重要工具簡單介紹

要更深入瞭解C++, 必須要知道一個程序從開始到結束都幹了些什麼, 怎麼幹的。 這篇博客從C++編譯到運行過程,解析下各個過程是怎樣進行的。

編譯過程

C++源程序-> 預編譯處理(.c)-> 編譯、優化程序(.asm、.s)-> 彙編程序(.obj、.o、.a)-> 鏈接程序(.exe等可執行文件)

  • 預編譯:處理源代碼中的以”#”開始的預編譯指令,如”#include”、”#define”等。
  • 編譯:對預處理完的文件按照詞法分析、語法分析、語義分析以及優化,進而產出相應的彙編代碼文件。這是程序構建的核心部分,也是最複雜的部分之一。
  • 彙編:將彙編代碼編譯成機器語言,一個彙編語句一般對應一條機器語句。
  • 鏈接:將多個目標文件連接起來形成可執行文件。

其中編譯可以分爲以下幾個步驟(編譯原理裏面的知識 /捂臉 )

  1. 詞法分析:將源代碼程序輸入掃描器,將源代碼字符序列分割成一系列記號(Token)。
  2. 語法分析:對產生的記號使用上下文無關語法進行語法分析,產生語法樹。語法分析:對產生的記號使用上下文無關語法進行語法分析,產生語法樹。
  3. 語義分析:進行靜態語義分析,通常包括聲明和類型的匹配,類型的轉換。語義分析:進行靜態語義分析,通常包括聲明和類型的匹配,類型的轉換。
  4. 中間語言生成:使用源代碼優化器將語法樹轉換成中間代碼並進行源碼級的優化。中間語言生成:使用源代碼優化器將語法樹轉換成中間代碼並進行源碼級的優化。
  5. 目標代碼生成:使用代碼生成器將中間代碼轉成依賴於具體機器的目標機器代碼。目標代碼生成:使用代碼生成器將中間代碼轉成依賴於具體機器的目標機器代碼。
  6. 目標代碼優化:使用目標代碼優化器對目標代碼進行優化,比如選擇合適的尋址方式、使用位移替代乘法、刪除多餘指令等。目標代碼優化:使用目標代碼優化器對目標代碼進行優化,比如選擇合適的尋址方式、使用位移替代乘法、刪除多餘指令等。

編譯器編譯源代碼生成的文件稱爲目標文件,目標文件是按照可執行文件的格式存儲的,linux下的目標文件和可執行文件可以看作是一種類型的文件,統稱爲ELF文件,一般有以下幾種:

  • 可重定位文件:如.o文件,包含代碼和數據,可以被鏈接成可執行文件或共享目標文件,靜態鏈接庫屬於這一類。
  • 可執行文件:如/bin/bash文件,包含了可以直接執行的程序,一般沒有擴展名。
  • 共享目標文件:如.so文件,包含代碼和數據,可以跟其他可重定位文件和共享目標文件鏈接產生新的目標文件,也可以跟可執行文件結合作爲進程映像的一部分。

鏈接過程

由彙編程序生成的目標文件並不能立即就被執行,其中可能還有許多沒有解決的問題。
  例如,某個源文件中的函數可能引用了另一個源文件中定義的某個符號(如變量或者函數調用等);在程序中可能調用了某個庫文件中的函數,等等。所有的這些問題,都需要經鏈接程序的處理方能得以解決。
  鏈接程序的主要工作就是將有關的目標文件彼此相連接,也即將在一個文件中引用的符號同該符號在另外一個文件中的定義連接起來,使得所有的這些目標文件成爲一個能夠誒操作系統裝入執行的統一整體。
  根據開發人員指定的同庫函數的鏈接方式的不同,鏈接處理可分爲兩種:
  (1)靜態鏈接
  在這種鏈接方式下,函數的代碼將從其所在地靜態鏈接庫中被拷貝到最終的可執行程序中。這樣該程序在被執行時這些代碼將被裝入到該進程的虛擬地址空間中。靜態鏈接庫實際上是一個目標文件的集合,其中的每個文件含有庫中的一個或者一組相關函數的代碼。
  (2)動態鏈接
  在此種方式下,函數的代碼被放到稱作是動態鏈接庫或共享對象的某個目標文件中。鏈接程序此時所作的只是在最終的可執行程序中記錄下共享對象的名字以及其它少量的登記信息。在此可執行文件被執行時,動態鏈接庫的全部內容將被映射到運行時相應進程的虛地址空間。動態鏈接程序將根據可執行程序中記錄的信息找到相應的函數代碼。
  對於可執行文件中的函數調用,可分別採用動態鏈接或靜態鏈接的方法。使用動態鏈接能夠使最終的可執行文件比較短小,並且當共享對象被多個進程使用時能節約一些內存,因爲在內存中只需要保存一份此共享對象的代碼。但並不是使用動態鏈接就一定比使用靜態鏈接要優越。在某些情況下動態鏈接可能帶來一些性能上損害。
  
總結:
   C++語言編譯的整個過程是非常複雜的,裏面涉及到的編譯器知識、硬件知識、工具鏈知識都是非常多的,深入瞭解整個編譯過程對工程師理解應用程序的編寫是有很大幫助的,希望大家可以多瞭解一些,在遇到問題時多思考、多實踐。
  一般情況下,我們只需要知道分成編譯和連接兩個階段,編譯階段將源程序(*.c)轉換成爲目標代碼(一般是obj文件),鏈接階段是把源程序轉換成的目標代碼(obj文件)與你程序裏面調用的庫函數對應的代碼連接起來形成對應的可執行文件(exe文件)就可以了,其他的都需要在實踐中多多體會纔能有更深的理解。

make & makefile

Makefile 是和 make 命令一起配合使用的.

很多大型項目的編譯都是通過 Makefile 來組織的, 如果沒有 Makefile, 那很多項目中各種庫和代碼之間的依賴關係不知會多複雜.

Makefile的組織流程的能力如此之強, 不僅可以用來編譯項目, 還可以用來組織我們平時的一些日常操作. 這個需要大家發揮自己的想象力。

詳情參見博客:https://www.cnblogs.com/wang_yb/p/3990952.html

cmake

你或許聽過好幾種 Make 工具,例如 GNU Make ,QT 的 qmake ,微軟的 MS nmake,BSD Make(pmake),Makepp,等等。這些 Make 工具遵循着不同的規範和標準,所執行的 Makefile 格式也千差萬別。這樣就帶來了一個嚴峻的問題:如果軟件想跨平臺,必須要保證能夠在不同平臺編譯。而如果使用上面的 Make 工具,就得爲每一種標準寫一次 Makefile ,這將是一件讓人抓狂的工作。

cmake就是針對上面問題所設計的工具:它首先允許開發者編寫一種平臺無關的 CMakeList.txt 文件來定製整個編譯流程,然後再根據目標用戶的平臺進一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。從而做到“Write once, run everywhere”。顯然,CMake 是一個比上述幾種 make 更高級的編譯配置工具。一些使用 CMake 作爲項目架構系統的知名開源項目有 VTK、ITK、KDE、OpenCV、OSG 等 [1]。

CMakeLists.txt 的語法比較簡單,由命令、註釋和空格組成,其中命令是不區分大小寫的。符號 # 後面的內容被認爲是註釋。命令由命令名稱、小括號和參數組成,參數之間使用空格進行間隔。

詳情參看博客:http://www.hahack.com/codes/cmake/

gdb調試命令

gcc -g  main.c                      //在目標文件加入源代碼的信息
gdb a.out       

(gdb) start                         //開始調試
(gdb) n                             //一條一條執行
(gdb) step/s                        //執行下一條,如果函數進入函數
(gdb) backtrace/bt                  //查看函數調用棧幀
(gdb) info/i locals                 //查看當前棧幀局部變量
(gdb) frame/f                       //選擇棧幀,再查看局部變量
(gdb) print/p                       //打印變量的值
(gdb) finish                        //運行到當前函數返回
(gdb) set var sum=0                 //修改變量值
(gdb) list/l 行號或函數名             //列出源碼
(gdb) display/undisplay sum         //每次停下顯示變量的值/取消跟蹤
(gdb) break/b  行號或函數名           //設置斷點
(gdb) continue/c                    //連續運行
(gdb) info/i breakpoints            //查看已經設置的斷點
(gdb) delete breakpoints 2          //刪除某個斷點
(gdb) disable/enable breakpoints 3  //禁用/啓用某個斷點
(gdb) break 9 if sum != 0           //滿足條件才激活斷點
(gdb) run/r                         //重新從程序開頭連續執行
(gdb) watch input[4]                //設置觀察點
(gdb) info/i watchpoints            //查看設置的觀察點
(gdb) x/7b input                    //打印存儲器內容,b--每個字節一組,7--7組
(gdb) disassemble                   //反彙編當前函數或指定函數
(gdb) si                            // 一條指令一條指令調試 而 s 是一行一行代碼
(gdb) info registers                // 顯示所有寄存器的當前值
(gdb) x/20 $esp                    //查看內存中開始的20個數

多種內存查看命令:
格式: x /nfu
x 是 examine 的縮寫
n表示要顯示的內存單元的個數
f表示顯示方式, 可取如下值

(1)x 按十六進制格式顯示變量。
(2)d 按十進制格式顯示變量。
(3)u 按十進制格式顯示無符號整型。
(4)o 按八進制格式顯示變量。
(5)t 按二進制格式顯示變量。
(6)a 按十六進制格式顯示變量。
(7) i 指令地址格式
(8) c按字符格式顯示變量。
(9) f 按浮點數格式顯示變量。

u表示一個地址單元的長度
(1).b表示單字節,
(2).h表示雙字節,
(3).w表示四字節,
(4).g表示八字節
比如:x/3xh buf

  • x/3xh buf 表示從內存地址buf讀取內容,3表示三個單位,x表示按十六進制顯示,h表示以雙字節爲一個單位。

多線程查看命令:
info threads //查看線程
thread thread_no //切換到線程號
thread apply all command //所有線程都執行命令打印棧楨
比如:thread apply all bt //所有線程都打印棧楨

更多請查看博客:https://blog.csdn.net/earbao/article/details/53958812

參考博客

  1. https://blog.csdn.net/weiyuefei/article/details/52056386
  2. https://blog.csdn.net/SYP35/article/details/77774279
  3. https://blog.csdn.net/earbao/article/details/53958812
  4. https://blog.csdn.net/tzshlyt/article/details/53668885
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章