天天看點

iOS開發你不知道的事-編譯&連結

對于平常的應用程式開發,我們很少需要關注

編譯

連結

過程。我們平常

Xcode

開發就是內建的的開發環境

(IDE)

,這樣的IDE一般都将

編譯

連結

的過程一步完成,通常将這種

編譯

連結

合并在一起的過程稱為

建構

,即使使用指令行來編譯一個源代碼檔案,簡單的一句

gcc hello.c

指令就包含了非常複雜的過程!

正是因為內建開發環境的強大,很多系統軟體的

運作機制與機理

被掩蓋,其程式的很多莫名其妙的錯誤讓我們無所适從,面對程式運作時種種性能瓶頸我們束手無策。我們看到的是這些問題的現象,但是卻很難看清本質,所有這些問題的本質就是

軟體運作背後的機理及支撐軟體運作的各種平台和工具

,如果能深入了解這些機制,那麼解決這些問題就能夠遊刃有餘。

編譯流程分析

現在我們通過一個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開發人員,都是要了解一下的。不需要你多麼牛逼,但是你能在平時的交流讨論,面試中能點出一個兩個相應的點,我相信絕對是逼格滿滿!