對于平常的應用程式開發,我們很少需要關注 編譯 連結 Xcode (IDE) 編譯 連結 編譯 連結 建構 gcc hello.c
和
過程。我們平常
開發就是內建的的開發環境
,這樣的IDE一般都将
的過程一步完成,通常将這種
合并在一起的過程稱為
,即使使用指令行來編譯一個源代碼檔案,簡單的一句
指令就包含了非常複雜的過程!
正是因為內建開發環境的強大,很多系統軟體的被掩蓋,其程式的很多莫名其妙的錯誤讓我們無所适從,面對程式運作時種種性能瓶頸我們束手無策。我們看到的是這些問題的現象,但是卻很難看清本質,所有這些問題的本質就是
運作機制與機理
,如果能深入了解這些機制,那麼解決這些問題就能夠遊刃有餘。
軟體運作背後的機理及支撐軟體運作的各種平台和工具
編譯流程分析
現在我們通過一個C語言的經典例子,來具體了解一下這些機制:
#include <stdio.h>
int main(){
printf("Hello World");
return 0;
}
在linux下隻需要一個簡單的指令(假設源代碼檔案名為hello.c):
$ gcc hello.c
$ ./a.out
Hello World
其實上述過程可以分解為四步:
- 預處理(Prepressing)
- 編譯(Compilation)
- 彙編(Assembly)
- 連結(Linking)
預編譯
首先是源代碼檔案
hello.c
和相關的頭檔案(如
stdio.h
等)被預編譯器
cpp
預編譯成一個
.i
檔案。第一步預編譯的過程相當于如下指令(-E 表示隻進行預編譯):
$ gcc –E hello.c –o hello.i
還可以下面的表達
$ cpp hello.c > hello.i
預編譯過程主要處理源代碼檔案中以”#”開頭的預編譯指令。比如
#include、#define
等,主要處理規則如下:
- 将所有的
删除,并展開所有的宏定義#define
- 處理所有條件預編譯指令,比如
#if,#ifdef,#elif,#else,#endif
- 處理
預編譯指令,将被包含的檔案插入到該預編譯指令的位置。#include
- 删除所有的注釋
//
/**/
- 添加行号和檔案名辨別,比如
#2 “hello.c” 2。
- 保留所有的
編譯器指令#pragma
截圖個大家看看效果
經過預編譯後的檔案
(.i檔案)
不包含任何宏定義,因為所有的宏已經被展開,并且包含的檔案也已經插入到
.i檔案
中,是以當我們無法判斷宏定義是否正确或頭檔案包含是否正确時,可以檢視預編譯後的檔案來确定問題。
編譯(compliation)
編譯過程就是把預處理完的檔案進行一系列的:
詞法分析
、
文法分析
語義分析
及
優化後生産相應的彙編代碼檔案
,此過程是整個程式建構的核心部分,也是最複雜的部分之一。其編譯過程相當于如下指令:
$ gcc –S hello.i –o hello.s
通過上圖我們不難得出,通過指令得到彙編輸出檔案
hello.s
.
彙編(assembly)
彙編器是将彙編代碼轉變成機器可以執行的指令,每一個彙編語句幾乎對應一條機器令。
是以彙編器的彙編過程相對于編譯器來講比較簡單,它沒複雜的文法,也沒有語義,也不需要做指令優化,隻是根據彙編指令和機器指令的對照表一一翻譯就可以了。其彙編過程相當于如下指令:
as hello.s –o hello.o
或者
gcc –c hello.s –o hello.o
或者使用
gcc
指令從
C源代碼檔案
開始,經過預編譯、編譯和彙編直接輸出目标檔案:
gcc –c hello.c –o hello.o
連結(linking)
連結通常是一個讓人比較費解的過程,為什麼彙編器不直接輸出可執行檔案而是輸出一個目标檔案呢?為什麼要連結?下面讓我們來看看怎麼樣調用
ld
才可以産生一個能夠正常運作的
Hello World
程式:
注意預設情況沒有gcc / 記得 :
$ brew install gcc
連結相應的庫
下面在貼出我們的寫出的源代碼是如何變成目标代碼的流程圖:
主要通過我們的編譯器做了以下任務:掃描、文法分析、語義分析、源代碼優化、代碼生成和目标代碼優化
到這我們就可以得到以下的檔案,不知道你是否有和我一起操作,玩得感覺還是不錯,繼續往下面看
iOS的編譯器
iOS現在為了達到更牛逼的速度和優化效果,采用了
LLVM
LLVM采用三相設計,前端Clang負責解析,驗證和診斷輸入代碼中的錯誤,然後将解析的代碼轉換為LLVM IR,後端LLVM編譯把IR通過一系列改進代碼的分析和優化過程提供,然後被發送到代碼生成器以生成本機機器代碼。
編譯器前端的任務是進行:
- 文法分析
- 語義分析
- 生成中間代碼(intermediate representation )
在這個過程中,會進行類型檢查,如果發現錯誤或者警告會标注出來在哪一行。
以上圖解内容所做的是事情和
gcc
編譯一模模一樣樣!
iOS程式-詳細編譯過程
- 1.寫入輔助檔案:将項目的檔案結構對應表、将要執行的腳本、項目依賴庫的檔案結構對應表寫成檔案,友善後面使用;并且建立一個
包,後面編譯後的檔案都會被放入包中;.app
- 2.運作預設腳本:
會預設一些腳本,當然你也可以自己預設一些腳本來運作。這些腳本都在Cocoapods
中可以看到;Build Phases
- 3.編譯檔案:針對每一個檔案進行編譯,生成可執行檔案
,這過程Mach-O
的完整流程,前端、優化器、後端;LLVM
- 4.連結檔案:将項目中的多個可執行檔案合并成一個檔案;
- 5.拷貝資源檔案:将項目中的資源檔案拷貝到目标包;
- 6.編譯
檔案:storyboard
檔案也是會被編譯的;storyboard
- 7.連結
檔案:将編譯後的storyboard
檔案連結成一個檔案;storyboard
- 8.編譯
檔案:我們的圖檔如果使用Asset
來管理圖檔,那麼這些圖檔将會被編譯成機器碼,除了Assets.xcassets
icon
;launchImage
- 9.運作
腳本:将在編譯項目之前已經編譯好的依賴庫和相關資源拷貝到包中。Cocoapods
- 10.生成
包.app
- 11.将
标準庫拷貝到包中Swift
- 12.對包進行簽名
- 13.完成打包
編譯過程的确是個比較複雜的過程,還有連結!并不是說難就不需要掌握,我個人建議每一個進階路上iOS開發人員,都是要了解一下的。不需要你多麼牛逼,但是你能在平時的交流讨論,面試中能點出一個兩個相應的點,我相信絕對是逼格滿滿!