對於平常的應用程式開發,我們很少需要關注編譯和連結過程。我們平常Xcode開發就是集成的的開發環境(IDE),這樣的IDE一般都將編譯和連結的過程一步完成,通常將這種編譯和連結合併在一起的過程稱為構建,即使使用命令行來編譯一個原始碼文件,簡單的一句gcc hello.c命令就包含了非常複雜的過程!
正是因為集成開發環境的強大,很多系統軟體的運行機制與機理被掩蓋,其程序的很多莫名其妙的錯誤讓我們無所適從,面對程序運行時種種性能瓶頸我們束手無策。我們看到的是這些問題的現象,但是卻很難看清本質,所有這些問題的本質就是軟體運行背後的機理及支撐軟體運行的各種平台和工具,如果能深入了解這些機制,那麼解決這些問題就能夠遊刃有餘,收放自如了。
編譯流程分析
現在我們通過一個C語言的經典例子,來具體了解一下這些機制:
在linux下只需要一個簡單的命令(假設原始碼文件名為hello.c):
其實上述過程可以分解為四步:
預編譯
首先是原始碼文件hello.c和相關的頭文件(如stdio.h等)被預編譯器cpp預編譯成一個.i文件。第一步預編譯的過程相當於如下命令(-E 表示只進行預編譯):
還可以下面的表達
預編譯過程主要處理原始碼文件中以」#」開頭的預編譯指令。比如#include、#define等,主要處理規則如下:
截圖個大家看看效果
經過預編譯後的文件(.i文件)不包含任何宏定義,因為所有的宏已經被展開,並且包含的文件也已經插入到.i文件中,所以當我們無法判斷宏定義是否正確或頭文件包含是否正確時,可以查看預編譯後的文件來確定問題。
編譯(compliation)
編譯過程就是把預處理完的文件進行一系列的:詞法分析、語法分析、語義分析及優化後生產相應的彙編代碼文件,此過程是整個程序構建的核心部分,也是最複雜的部分之一。其編譯過程相當於如下命令:
通過上圖我們不難得出,通過命令得到彙編輸出文件hello.s.
彙編(assembly)
彙編器是將彙編代碼轉變成機器可以執行的指令,每一個彙編語句幾乎對應一條機器令。所以彙編器的彙編過程相對於編譯器來講比較簡單,它沒複雜的語法,也沒有語義,也不需要做指令優化,只是根據彙編指令和機器指令的對照表一一翻譯就可以了。其彙編過程相當於如下命令:
或者
或者使用gcc命令從C原始碼文件開始,經過預編譯、編譯和彙編直接輸出目標文件:
連結(linking)
連結通常是一個讓人比較費解的過程,為什麼彙編器不直接輸出可執行文件而是輸出一個目標文件呢?為什麼要連結?下面讓我們來看看怎麼樣調用ld才可以產生一個能夠正常運行的Hello World程序:
連結相應的庫
下面在貼出我們的寫出的原始碼是如何變成目標代碼的流程圖:
主要通過我們的編譯器做了以下任務:掃描、語法分析、語義分析、原始碼優化、代碼生成和目標代碼優化
到這我們就可以得到以下的文件,不知道你是否有和我一起操作,玩得感覺還是不錯,繼續往下面看
iOS的編譯器
iOS現在為了達到更牛逼的速度和優化效果,採用了LLVM
LLVM採用三相設計,前端Clang負責解析,驗證和診斷輸入代碼中的錯誤,然後將解析的代碼轉換為LLVM IR,後端LLVM編譯把IR通過一系列改進代碼的分析和優化過程提供,然後被發送到代碼生成器以生成本機機器代碼。
編譯器前端的任務是進行:
在這個過程中,會進行類型檢查,如果發現錯誤或者警告會標註出來在哪一行。
以上圖解內容所做的是事情和gcc編譯一模模一樣樣!
iOS程序-詳細編譯過程