一探·编译与连接
1.前言
對于平常應用程序的開發,我們很少需要關注編譯與鏈接過程,因為通常的開發環境都是流行的集成開發環境,比如Visual Studio、Delphi等。他們往往將編譯與鏈接的過程放在了一起,稱為構建(Build)。 IDE和編譯器提供的默認配置、編譯、鏈接參數對于大部分的應用程序開發而言應經足夠使用了。但只在這樣的開發過程我們往往會被這些復雜的集成工具所提供的強大功能所迷惑,很多系統軟件的運行機制或機理被掩蓋,其程序的很多莫名其妙的錯誤讓我們無所適從。此外,面對程序運行時種種性能瓶頸我們束手無策。這是因為我們往往僅看到了問題的現象,卻沒有看清問題的本質,所有這些問題的的本質就是然間運行背后的機理及支撐軟件運行的各種平臺與工具。如果能夠深入了解這些機制,那么解決這些問題我們就游刃有余啦。2.被掩藏了的過程是什么?
C語言中的經典“HelloWorld”程序如下所示: #include<stdio.h> int main() {printf("HelloWorld\n");return 0; } 當我們采用集成開發環境進行編譯時,實際上可以分解為四個步驟:預處理Preprecessing)、編譯(Compilation)、匯編(Assembly)、鏈接(Linking)。利用GCC編譯,其編譯過程如下圖所示:預編譯: 首先是源代碼文件hello.cpp和相關頭文件(如stdio.h等)被編譯器cpp預編譯成一個.ii文件。 預編譯過程主要處理那些源代碼文件中的以“#”開始的預編譯指令。比如“#include”、“#define”等,主要處理規則如下: 1.將所有的“define”刪除,并且展開所有的宏定義; 2.處理所有條件預編譯指令,比如“#if”、“#ifdef”、“elif”、“#else”、“#endif”; 3.處理“#include”預編譯指令,將被包含的文件插入到該預編譯指令的位置,注意,這個過程是遞歸進行的。也就是說,被包含的文件可能還包含其他文件; 4.刪除所有注釋“//”和“/**/”; 5.添加行號和文件名標識,比如#2 “hello.cpp”2,以便于編譯時編譯器產生調試用的行號信息及用于編譯時產生編譯錯誤或警告時能夠顯示行號; 6.保留所有的#pragma編譯器指令,因為編譯器需要使用它們; 經過預編譯后的.ii文件不包括任何宏定義,因為所有的宏已經被展開,并且包含的文件已經被插入到.ii文件中。所以當我們無法判斷宏定義是否正確?頭文件是否包含正確?可以查看預編譯后的文件來確定問題。 編譯: 編譯的過程就是把與處理完的文件進行一系列詞法分析、語法分析、語義分析、及優化后生成相適應的匯編代碼文件。這個過程往往是我們所說的整個程序構建的核心過程,也是最復雜的部分之一。這會涉及到編譯原理的一些內容。得到的匯編輸出文件為hello.s。 匯編: 匯編器是將匯編代碼轉變成機器可以執行的指令,每一個匯編語句幾乎都對應一條機器指令。所以匯編器的匯編過程相對于編譯器來講比較簡單,他沒有復雜的語法,也沒有語義,也不需要做指令優化,只需要根據匯編指令和機器指令的對照表一一進行翻譯就可以了,“匯編”這個名字也來源于此。 鏈接: 鏈接過程通常會讓人比較費解,為什么匯編器不直接輸出可執行文件,而是輸出一個目標文件呢???鏈接過程到底包含了什么內容???為什么要鏈接???這些問題看似簡單,實際上涉及了編譯、鏈接和庫的內容,甚至是操作系統一些很底層的東西。我們僅僅記住一個事就好,可執行文件不僅僅需要一個目標文件(你的意圖)還需要包含系統的庫文件(可執行文件是在操作系統上運行的)!!!后面,我會用很大篇幅詳細說明這個問題。 現在,我們基本上知道了一個源文件到可執行文件的過程,以及中間產生的臨時文件。現在的問題是,編譯器到底做了什么事???
總結
- 上一篇: 不是内部或外部命令也不是可运行的程序?
- 下一篇: ipmsg飞鸽传书系统即时通讯