C++從代碼到可執行程序

1. 從代碼到可執行程序的執行

無論是windows操作系統還是linux操作系統,C++編寫代碼到可執行程序的執行,都經過了預處理、編譯、彙編、鏈接、運行5個步驟。有時候我們也將預編譯、編譯和彙編統稱爲編譯。下面分別介紹這5個步驟

預處理

預處理過程進行的操作:

1. 將所有的“#define”刪除,並且展開所有的宏定義
2. 處理所有的條件編譯指令,比如“#if”、“#ifdef”、“#elif”、“#else”、“#endif”
3. 處理“#include”預編譯指令,將被包含的頭文件插入到該編譯指令的位置。(這個過程是遞歸進行的,因爲被包含的文件可能還包含了其他文件)
4. 刪除所有的註釋“//”和“/* */”。
5. 添加行號和文件名標識,方便後邊編譯時編譯器產生調試用的行號信息以及編譯時產生編譯錯誤或警告時能夠顯示行號。
6. 保留所有的#pragma編譯指令,因爲編譯器需要使用它們。

編譯

編譯過程就是把預處理完的文件進行一系列的詞法分析、語法分析、語義分析以及優化後產生相應的彙編代碼文件,這個過程是整個程序構建的核心部分,也是最複雜的部分之一。爲了使計算機能執行高級語言源程序,必須先用一種稱爲“編譯器(complier)”的軟件(也稱編譯程序或編譯系統)。編譯是以源程序文件爲單位單別編譯的,頭文件不參加編譯。(在VC6.0裏如果編譯頭文件則會彈出沒有可以工具函數,在VS2013中,對於頭文件,編譯按鈕爲灰色,不可用狀態。)

現在編譯器種類很多,不同編譯器區別在於對編譯過程做了優化,添加了一些庫函數或類庫。優化處理是編譯系統中一項比較艱深的技術。它涉及到的問題不僅同編譯技術本身有關,而且同機器的硬件環境也有很大的關係。優化一部分是對中間代碼的優化。這種優化不依賴於具體的計算機。另一種優化則主要針對目標代碼的生成而進行的。對於前一種優化,主要的工作是刪除公共表達式、循環優化(代碼外提、強度削弱、變換循環控制條件、已知量的合併等)、複寫傳播,以及無用賦值的刪除,等等。後一種類型的優化同機器的硬件結構密切相關,最主要的是考慮是如何充分利用機器的各個硬件寄存器存放的有關變量的值,以減少對於內存的訪問次數。另外,如何根據機器硬件執行指令的特點(如流水線、RISC、CISC、VLIW等)而對指令進行一些調整使目標代碼比較短,執行的效率比較高,也是一個重要的研究課題。

編譯技巧:編譯的作用是對源程序進行詞法檢查、語法檢查和中間代碼生成。編譯時對文件中的全部內容進行檢查,如果有語法錯誤,編譯結束後會顯示出所有的編譯出錯信息,開發人員可以根據錯誤提示修改程序。對於新寫的一個保護多個文件的工程,一開始採用源文件分別編譯,這樣容易發現每個源文件的自身錯誤,限定了錯誤的範圍,如果一開始就採用全部編譯,多個源文件可能會產生許多錯誤,無形中增加了開發難度。如果每個源文件都通過了編譯,再將所有文件進行編譯。對源文件分別編譯對於調試,糾錯是一種很好的方法。

彙編

彙編實際上指把彙編語言代碼翻譯成目標機器指令的過程。彙編器的編譯過程相對於編譯器來講比較簡單,它沒有複雜的語法,也沒有語義,也不需要做指令優化,只是根據彙編指令和機器指令的對照表一一翻譯。對於被翻譯系統處理的每一個語言源程序,都將最終經過這一處理而得到相應的目標文件。目標程序一般以.obj或.o作爲後綴,這具體看操作系統,如Windows是下是.obj目標文件,Linux下是.o目標文件。目標文件中所存放的也就是與源程序等效的目標機器語言代碼

鏈接

前面提到過,編譯是對源文件分別進行的,每個源文件都產生一個目標文件。但由彙編程序生成的目標文件並不能立即就被執行,因爲各個源文件之間可能是有相互聯繫的,例如,某個源文件中的函數可能引用了另一個源文件中定義的某個符號(如變量或者函數調用等);在程序中可能調用了某個庫文件中的函數,等等。所有的這些問題都需要經鏈接解決,即將源程序產生的多個目標文件鏈接爲一個整體。即通過系統提供的“連接程序(linker)”將一個程序的所有目標程序和系統的庫文件以及系統提供的其他信息連接起來,最終形成一個可執行的二進制文件,它的後綴是.exe,此時產生了完整的執行文件。

鏈接程序的主要工作就是將有關的目標文件彼此相連接,如源文件產生的目標文件和庫文件等,使得所有的這些目標文件成爲一個能夠被操作系統裝入執行的統一整體。根據指定的庫函數的不同,鏈接處理可分爲兩種:

(1)靜態鏈接:在這種鏈接方式下,函數的代碼將從其所在地靜態鏈接庫中被拷貝到最終的可執行程序中。這樣該程序在被執行時這些代碼將被裝入到該進程的虛擬地址空間中。靜態鏈接庫實際上是一個目標文件的集合,其中的每個文件含有庫中的一個或者一組相關函數的代碼。

(2)動態鏈接:此種方式下,函數的代碼被放到稱作是動態鏈接庫或共享對象的某個目標文件中。鏈接程序此時所作的只是在最終的可執行程序中記錄下共享對象的名字以及其它少量的登記信息。在此可執行文件被執行時,動態鏈接庫的全部內容將被映射到運行時相應進程的虛地址空間。動態鏈接程序將根據可執行程序中記錄的信息找到相應的函數代碼。

對於可執行文件中的函數調用,可分別採用動態鏈接或靜態鏈接的方法。使用動態鏈接能夠使最終的可執行文件比較短小,並且當共享對象被多個進程使用時能節約一些內存,因爲在內存中只需要保存一份此共享對象的代碼。但並不是使用動態鏈接就一定比使用靜態鏈接要優越。 

鏈接將相關關聯文件鏈接起來,所以這個階段的錯誤不好調試,發生錯誤可能在我們自己編寫的代碼中,也有可能是與別的文件關聯產生的,對於因關聯產生錯誤就比較複雜了,有時需要調整編譯器或鏈接器。

運行

運行階段就比較簡單了,直接執行前面鏈接過程產生的可執行的二進制文件即可得到運行結果

 

 

2.Windows下代碼到可執行程序的過程

1 .cpp經過預處理生成.i文件經過編譯生成.asm文件經過彙編生成.obj文件(COFF格式的二進制目標文件) 經過鏈接(和其他obj文件及靜態鏈接庫文件)生成PE格式的可執行文件

2. 編譯階段,包括預處理、編譯、彙編,由cl.exe完成。

3. 鏈接階段由,link.exe完成。

 

3. Linux下代碼到可執行程序的過程

 

 

 

 

發佈了62 篇原創文章 · 獲贊 150 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章