iOS編譯原理

主要內容:

  1. 理解CC++以及OC的關系
  2. 編譯型語言與解釋型語言
  3. 編譯器LLVMCLang
  4. 理解iOS編譯流程
  5. 預處理
  6. 編譯
  7. 匯編
  8. 鏈接

一、理解C、C++以及OC的關系

1.C語言
  1. C語言是一門面向過程的計算機編程語言,既可用于系統軟件開發,也適用于應用軟件開發;
  2. C語言編譯器普遍存在于各種不同的操作系統中,例如Microsoft Windows,Mac OS X, Linux, Unix等;
  3. C語言的設計影響了眾多后來的編程語言,例如C++Objective-CJavaC#等;
2.C++語言
  1. 兼容了C語言面向過程特點,但又進行了擴充和完善;
  2. 作為一種面向對象的語言,具有封裝、多繼承、多態等特性;
3.Objective-C語言
  1. 擴展了C語言的能力,使其具備面向對象設計的能力,相當于C的超集;
  2. OC代碼中也可以有CC++語句,它可以調用C函數,也可以通過C++對象訪問方法;
4.OC與C++的比較
  1. OCC++都是從C語言演變而來面向對象設計語言,也都兼容標準的C語言;但它們屬于不同的面向對象學派;
  2. 兩者最大的不同在于:OC提供了運行時的動態綁定機制,而C++是編譯時靜態綁定,并通過嵌入類和虛函數來模擬實現;
  3. OC在編譯階段降低了編譯要求提高了靈活性,而C++則是提高了編譯要求,在編譯過程中就發現更多的潛在錯誤,在運行前改正,降低了靈活性;

以下面的代碼為例,在編譯期間,C++認為是錯誤的,而OC則認為沒有問題:

NSString *test =(id) [[NSArray alloc] init];

OCC++在使用細節上的不同如下:

  1. 定型:OC是動態定型,可以允許根據字符串名字來訪問方法和類,還可以動態鏈接和添加類;
  2. 繼承:OC不支持多繼承,C++支持多繼承;
  3. 函數調用:OC通過消息傳遞實現函數調用,而C++直接進行函數調用;
  4. 接口:OC采用Protocol形式來定義接口,而C++采用虛函數形式來定義接口;
  5. 重載:OC不允許同一個類中兩個方法有相同的名字(即使只是參數類型不同),但C++可以;

二、編譯型語言與解釋型語言

Objective-C屬于編譯型語言,這是為了保證iPhone的執行效率;

1.編譯型語言
  1. 程序運行前,必須先通過編譯器生成機器碼,機器碼直接通過CPU執行,運行時不需要重新翻譯;
  2. 程序執行效率高,但依賴編譯器,調試周期長、跨平臺性差些;
  3. 代表語言:CC++OC等;
2.解釋型語言
  1. 程序運行前,不需要進行編譯,而是以文本方式存儲程序代碼,運行時需要解釋器解釋后再運行;
  2. 程序執行效率低下,但是程序具有動態性,運行后也可以隨時增加和更新代碼來改變程序邏輯;
  3. 代表語言:JavascriptPython等;
編譯原理-語言的分類

三、編譯器LLVM與CLang

1.編譯器

概念:把一種編程語言(原始語言)轉換為另一種編程語言(目標語言)的程序;

大多數編譯器都分前端后端兩部分:

  • 前端:負責詞法分析語法分析生成中間代碼
  • 后端:以中間代碼作為輸入,進行與架構無關的代碼優化,接著針對不同架構生成不同的機器碼;

補充:

  1. 前后端以中間代碼作為媒介,使得前后端可以獨立的變化,互不影響;
  2. 這樣的好處在于:新增一門語言只需要修改前端,而新增一種CPU架構只需要修改后端即可;
2.LLVM與Clang

LLVM是蘋果當前使用的編譯器:

  1. LLVM是一套編譯器基礎設施項目,為自由軟件,以C++寫成,包含一系列模塊化的編譯器組件和工具鏈,用來開發編譯器前端后端
  2. 基于 LLVM 衍生出了一些強大的子項目,比如:ClangLLDB

CLang基于LLVM,是一個高度模塊化開發的輕量級編譯器;

  1. CLang主要來自蘋果電腦的支持,同時支持CObjective-C以及C++
  2. CLang用于替代Xcode5版本前使用的GCC,編譯速度提高了3倍:
3.理解iOS中的編譯器
  1. iOS開發中,通常LLVM被認為是編譯器的后端,而Clang是作為編譯器的前端;
  2. 二者以 IR(中間代碼)作為媒介,這樣前后端分離,使得前后端可以獨立的變化,互不影響;
  3. C 語言家族的前端是 clangswift 的前端是 swiftc,但二者的后端都是 LLVM

四、理解iOS編譯流程

1.編譯流程圖

LLVM的編譯過程相當復雜,iOS代碼運行需要經過:預處理編譯匯編鏈接四個關鍵階段,具體的流程如下圖:

編譯原理-編譯流程
2.準備測試文件

OC語言為例,詳細分析代碼的編譯流程,準備一個main.m文件的內容如下:

#import <Foundation/Foundation.h>
/// 增加注釋:宏定義Name
#define Name "梧雨北辰"
int main(int argc, const char * argv[]) {
    NSLog(@"Hello, %s", Name);
    return 0;
}

五、預處理(Prepressing)

1.主要功能
  1. 替換宏:替換代碼中各種宏定義,如定義的常量、函數等;
  2. 導入頭文件:將#include包含的文件插入到該指令位置等;
  3. 清理注釋:刪除所有注釋:///* */等;
  4. 條件編譯:處理#if#ifdef#endif等類似的條件編譯;
  5. 添加行號和文件名標識:以便于編譯時編譯器能夠顯示警告和錯誤的所在行號;
2.查看預處理結果

使用xcrun命令,在終端執行預處理操作:

xcrun clang -E main.m

終端顯示效果如下:

# 1 "main.m"
# 1 "<built-in>" 1

...

# 1 "/Applications/Xcode13.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/FoundationLegacySwiftCompatibility.h" 1 3
# 193 "/Applications/Xcode13.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3
# 2 "main.m" 2


int main(int argc, const char * argv[]) {
 NSLog(@"Hello, %s", "梧雨北辰");
 return 0;
}

結果分析:

  1. 預處理后的文件中,注釋已經被清理,宏定義也已經被替換;
  2. 預處理后的文件有很多行,因為該過程中導入了頭文件(Foundation.h),而且這個過程是遞歸的;

六、編譯(Compilation)

1. 詞法分析(Lexical Analysis)

主要功能:通過掃描器,分割識別源代碼符號(如大小括號、=、字符串);

使用xcrun命令,在終端執行詞法分析操作:

xcrun clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m

終端顯示效果如下:

annot_module_include '#import <Foundation/Foundation.h>
/'      Loc=<main.m:1:1>
int 'int'    [StartOfLine]  Loc=<main.m:4:1>
identifier 'main'    [LeadingSpace]

......

r_brace '}'  [StartOfLine]  Loc=<main.m:7:1>
eof ''      Loc=<main.m:10:1>

結果分析:

  1. 每個被分割的源代碼符號都被記錄了位置,方便后續定位錯誤;
  2. 比如Loc=<main.m:4:1> 就表示:'int'這個符號是從源文件main.m的第4行的第1個字符開始的;
2.語法分析(Semantic Analysis)

主要功能:對源代碼符號進行分析,驗證語法是否正確,最后生成AST語法樹;

使用xcrun命令,查看語法分析結果:

xcrun clang -fsyntax-only -Xclang -ast-dump main.c | open -f

AST語法樹:

  1. 是抽象語法樹,結構上比代碼更精簡,遍歷速度更快;
  2. 能夠更快的進行靜態檢查,同時生成IR(中間代碼);
3.靜態分析(Static Analysis)

主要功能:對AST樹進行遍歷分析,包括類型檢查方法實現檢查,會及時提示錯誤;

4.生成中間代碼(Code Generation)

主要功能:CodeGen負責將AST語法樹自頂向下遍歷,逐步翻譯成IR中間代碼;

IR中間代碼:

  1. 這是一種更接近于機器碼的語言,使得編譯器被分為前端和后端,不同的平臺可以利用各自的編譯器將中間代碼,轉化為適合不同平臺的機器碼;
  2. 對于iOS系統來說,IR中間代碼生成的就是Mach-O可執行文件;
  3. IR是前端的輸出,后端的輸入;

七、匯編(Assembly)

輸出中間代碼標志著前端工作的完成,接下來將進入后端的處理流程。

1.LLVM優化中間代碼

中間代碼IR進入后端,LLVM會對其進行優化:

  1. Optimization Level
  2. bitcode
2.生成匯編代碼

LLVMIR進行優化后,會針對不同架構生成不同匯編代碼;

匯編階段的目的:

  1. 將代碼匯編化,并將符號進行歸類;
  2. 將外部導入符號,放到重定位符號表;
  3. 最后生成一個或多個.o目標文件;

使用xcrun命令,生成匯編文件:

xcrun clang -S main.m -o main.s

打開.s文件,摘取內容如下:

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 11, 0 sdk_version 11, 3
    .globl  _main                           ## -- Begin function main
    
    // ......

    callq   _NSLog

    // ......
.subsections_via_symbols

可以看到,匯編文件中的NSLog操作已經被轉化為匯編命令形式的調用,即callq _NSLog

3.生成目標文件

該階段是匯編器匯編代碼轉換為機器代碼,并輸出目標文件,即.o文件;

使用xcrun命令,生成目標文件:

xcrun clang -fmodules -c main.m -o main.o

使用file命令,查看目標文件類型:

% file main.o
main.o: Mach-O 64-bit object x86_64

可以看到,匯編器生成Mach-O格式的文件,而且是object類型,即目標文件類型:

  1. Mach-O文件是用于iOSOS平臺上的文件類型;
  2. Mach-O作為a.out格式的替代,提供了更強的擴展性,也提升了符號表中信息的訪問速度;

使用xcrun命令,查看下main.o中的符號:

xcrun nm -nm main.o

終端顯示效果如下:

                 (undefined) external _NSLog
                 (undefined) external ___CFConstantStringClassReference
0000000000000000 (__TEXT,__text) external _main

可以看到,此時我們使用的NSLog函數,對應著_NSLog符號:

  1. undefined:表示在當前文件暫時找不到符號_NSLog
  2. external:表示這個符號是外部可以訪問的,對應表示文件私有的符號是non-external

八、鏈接(Linking)

主要功能:符號解析、重定位、合并目標文件,最終生成可執行文件;

1.使用xcrun命令執行鏈接,得到可執行文件
xcrun clang main.o -o main
2.使用file命令,查看文件類型
% file main
main: Mach-O 64-bit executable x86_64
% ./main
2021-10-01 19:06:41.846 main[5663:660299] Hello, 梧雨北辰

結果分析:雖然還是Mach-O格式,但此時已經是executable類型了,即可執行文件。而且運行該文件后也打印出了預期的結果;

3.再次使用xcrun命令,查看可執行文件的符號表
% xcrun nm -nm main
                 (undefined) external _NSLog (from Foundation)
                 (undefined) external ___CFConstantStringClassReference (from CoreFoundation)
                 (undefined) external dyld_stub_binder (from libSystem)
0000000100000000 (__TEXT,__text) [referenced dynamically] external __mh_execute_header
0000000100003f40 (__TEXT,__text) external _main
0000000100008008 (__DATA,__data) non-external __dyld_private

結果分析:_NSLog符號依然是undefined,不過此時多了一些信息,即from Foundation,表示這個符號來自于Foundation,會在運行時動態綁定;

4.鏈接階段的主要任務

1.符號解析

將每個符號引用和對應的符號定義關聯起來;

  • 鏈接器鏈接多文件時會創建符號表,用于記錄所有已經定義和未定義的符號;
    1. 出現相同符號,會報錯:"ld:dumplicate symbols"
    2. 在其他目標文件里沒有找到到符號,會報錯:"Undefined symbols"
  • 另外,鏈接器在整理函數的符號調用關系時,可以幫助我們理清那些函數沒有被調用,并自動去除掉;

2.重定位

將變量名、函數名這些符號定義與一個內存位置關聯起來;

  • 因為只有通過了綁定,機器才知道需要操作什么內存地址;
  • 否則,我們就需要在寫代碼時給每個指令設置好內存地址,不僅操作繁瑣,而且容易引起出錯;

3.合并目標文件

將多個.m文件編譯產生的.o目標文件與其他Mach-O文件(如dylibatbd),合成一個Mach-O格式的可執行文件;

  • 通常項目都會包含多個文件,不同文件之間的變量接口函數就會產生相互依賴關系;
  • 程序運行前,需要使用鏈接器將多個文件里的符號和地址綁定起來,才能保證整個程序里的變量、接口的正常調用;
5.理解靜態鏈接與動態鏈接

靜態鏈接:作用于編譯期,鏈接后的文件依然可能會存在一些"undefined"的符號。但是這些符號都會被記錄下來,在運行時再通過dlopendlsym動態鏈接綁定;

動態鏈接:作用于運行時,這樣的優勢在于:諸多類似UIKit這樣的共享庫將不必包含在每一個App包里。比如:我們使用到的UIKit系統庫,等到點擊App真正開始運行之前,才會去鏈接依賴的UIKit,鏈接完成再運行App

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
禁止轉載,如需轉載請通過簡信或評論聯系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容