主要內(nèi)容:
- 理解
C
错邦、C++
以及OC
的關(guān)系 - 編譯型語言與解釋型語言
- 編譯器
LLVM
與CLang
- 理解
iOS
編譯流程 - 預處理
- 編譯
- 匯編
- 鏈接
一腻豌、理解C家坎、C++以及OC的關(guān)系
1.C語言
-
C
語言是一門面向過程的計算機編程語言嘱能,既可用于系統(tǒng)軟件開發(fā),也適用于應(yīng)用軟件開發(fā)虱疏; -
C
語言編譯器普遍存在于各種不同的操作系統(tǒng)中惹骂,例如Microsoft Windows
,Mac OS X
,Linux
,Unix
等; -
C
語言的設(shè)計影響了眾多后來的編程語言做瞪,例如C++
对粪、Objective-C
、Java
装蓬、C#
等著拭;
2.C++語言
- 兼容了
C
語言面向過程特點,但又進行了擴充和完善牍帚; - 作為一種面向?qū)ο蟮恼Z言儡遮,具有封裝、多繼承暗赶、多態(tài)等特性鄙币;
3.Objective-C語言
- 擴展了
C
語言的能力,使其具備面向?qū)ο笤O(shè)計的能力蹂随,相當于C
的超集十嘿; -
OC
代碼中也可以有C
和C++
語句,它可以調(diào)用C
函數(shù)岳锁,也可以通過C++
對象訪問方法详幽;
4.OC與C++的比較
-
OC
與C++
都是從C
語言演變而來面向?qū)ο笤O(shè)計語言,也都兼容標準的C
語言浸锨;但它們屬于不同的面向?qū)ο髮W派唇聘; - 兩者最大的不同在于:
OC
提供了運行時的動態(tài)綁定機制,而C++
是編譯時靜態(tài)綁定柱搜,并通過嵌入類和虛函數(shù)來模擬實現(xiàn)迟郎; -
OC
在編譯階段降低了編譯要求提高了靈活性,而C++
則是提高了編譯要求聪蘸,在編譯過程中就發(fā)現(xiàn)更多的潛在錯誤宪肖,在運行前改正,降低了靈活性健爬;
以下面的代碼為例控乾,在編譯期間,C++
認為是錯誤的娜遵,而OC
則認為沒有問題:
NSString *test =(id) [[NSArray alloc] init];
OC
與C++
在使用細節(jié)上的不同如下:
- 定型:
OC
是動態(tài)定型蜕衡,可以允許根據(jù)字符串名字來訪問方法和類,還可以動態(tài)鏈接和添加類设拟; - 繼承:
OC
不支持多繼承慨仿,C++
支持多繼承久脯; - 函數(shù)調(diào)用:
OC
通過消息傳遞實現(xiàn)函數(shù)調(diào)用,而C++
直接進行函數(shù)調(diào)用镰吆; - 接口:
OC
采用Protocol
形式來定義接口帘撰,而C++
采用虛函數(shù)形式來定義接口; - 重載:
OC
不允許同一個類中兩個方法有相同的名字(即使只是參數(shù)類型不同)万皿,但C++
可以摧找;
二、編譯型語言與解釋型語言
Objective-C
屬于編譯型語言牢硅,這是為了保證iPhone
的執(zhí)行效率蹬耘;
1.編譯型語言
- 程序運行前,必須先通過
編譯器
生成機器碼
唤衫,機器碼直接通過CPU
執(zhí)行婆赠,運行時不需要重新翻譯; - 程序執(zhí)行效率高佳励,但依賴編譯器休里,調(diào)試周期長、跨平臺性差些赃承;
- 代表語言:
C
妙黍、C++
、OC
等瞧剖;
2.解釋型語言
- 程序運行前拭嫁,不需要進行編譯,而是以文本方式存儲程序代碼抓于,運行時需要解釋器解釋后再運行做粤;
- 程序執(zhí)行效率低下,但是程序具有動態(tài)性捉撮,運行后也可以隨時增加和更新代碼來改變程序邏輯怕品;
- 代表語言:
Javascript
、Python
等巾遭;
三肉康、編譯器LLVM與CLang
1.編譯器
概念:把一種編程語言(原始語言
)轉(zhuǎn)換為另一種編程語言(目標語言)的程序;
大多數(shù)編譯器都分前端
和后端
兩部分:
- 前端:負責
詞法分析
、語法分析
灼舍、生成中間代碼
吼和; - 后端:以
中間代碼
作為輸入,進行與架構(gòu)無關(guān)的代碼優(yōu)化骑素,接著針對不同架構(gòu)生成不同的機器碼炫乓;
補充:
- 前后端以
中間代碼
作為媒介,使得前后端可以獨立的變化,互不影響厢岂; - 這樣的好處在于:新增一門語言只需要修改前端光督,而新增一種
CPU
架構(gòu)只需要修改后端即可阳距;
2.LLVM與Clang
LLVM
是蘋果當前使用的編譯器:
-
LLVM
是一套編譯器基礎(chǔ)設(shè)施項目塔粒,為自由軟件,以C++
寫成筐摘,包含一系列模塊化的編譯器組件和工具鏈卒茬,用來開發(fā)編譯器前端
和后端
; - 基于
LLVM
衍生出了一些強大的子項目咖熟,比如:Clang
和LLDB
圃酵。
CLang
基于LLVM
,是一個高度模塊化開發(fā)的輕量級編譯器馍管;
-
CLang
主要來自蘋果電腦的支持郭赐,同時支持C
芍阎、Objective-C
以及C++
刺下; -
CLang
用于替代Xcode5
版本前使用的GCC
鉴未,編譯速度提高了3
倍:
3.理解iOS中的編譯器
- 在
iOS
開發(fā)中饶氏,通常LLVM
被認為是編譯器的后端述寡,而Clang
是作為編譯器的前端鬼雀; - 二者以
IR
(中間代碼)作為媒介实撒,這樣前后端分離靴患,使得前后端可以獨立的變化桨菜,互不影響豁状; -
C
語言家族的前端是clang
,swift
的前端是swiftc
倒得,但二者的后端都是LLVM
泻红;
四、理解iOS編譯流程
1.編譯流程圖
LLVM的編譯過程相當復雜霞掺,iOS
代碼運行需要經(jīng)過:預處理
谊路、編譯
、匯編
根悼、鏈接
四個關(guān)鍵階段凶异,具體的流程如下圖:
2.準備測試文件
以OC
語言為例,詳細分析代碼的編譯流程挤巡,準備一個main.m
文件的內(nèi)容如下:
#import <Foundation/Foundation.h>
/// 增加注釋:宏定義Name
#define Name "梧雨北辰"
int main(int argc, const char * argv[]) {
NSLog(@"Hello, %s", Name);
return 0;
}
五剩彬、預處理(Prepressing)
1.主要功能
- 替換宏:替換代碼中各種宏定義,如定義的常量矿卑、函數(shù)等喉恋;
- 導入頭文件:將
#include
包含的文件插入到該指令位置等; - 清理注釋:刪除所有注釋:
//
、/*
*/
等轻黑; - 條件編譯:處理
#if
糊肤、#ifdef
,#endif
等類似的條件編譯氓鄙; - 添加行號和文件名標識:以便于編譯時編譯器能夠顯示警告和錯誤的所在行號馆揉;
2.查看預處理結(jié)果
使用xcrun
命令,在終端執(zhí)行預處理操作:
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;
}
結(jié)果分析:
- 預處理后的文件中抖拦,注釋已經(jīng)被清理升酣,宏定義也已經(jīng)被替換;
- 預處理后的文件有很多行态罪,因為該過程中導入了頭文件(
Foundation.h
)噩茄,而且這個過程是遞歸的;
六复颈、編譯(Compilation)
1. 詞法分析(Lexical Analysis)
主要功能:通過掃描器绩聘,分割識別源代碼符號(如大小括號、=
耗啦、字符串)凿菩;
使用xcrun
命令,在終端執(zhí)行詞法分析操作:
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>
結(jié)果分析:
- 每個被分割的源代碼符號都被記錄了位置芹彬,方便后續(xù)定位錯誤蓄髓;
- 比如
Loc=<main.m:4:1>
就表示:'int'
這個符號是從源文件main.m
的第4
行的第1
個字符開始的;
2.語法分析(Semantic Analysis)
主要功能:對源代碼符號進行分析舒帮,驗證語法是否正確会喝,最后生成AST
語法樹;
使用xcrun
命令玩郊,查看語法分析結(jié)果:
xcrun clang -fsyntax-only -Xclang -ast-dump main.c | open -f
AST
語法樹:
- 是抽象語法樹肢执,結(jié)構(gòu)上比代碼更精簡,遍歷速度更快译红;
- 能夠更快的進行靜態(tài)檢查预茄,同時生成
IR
(中間代碼);
3.靜態(tài)分析(Static Analysis)
主要功能:對AST
樹進行遍歷分析侦厚,包括類型檢查
耻陕、方法實現(xiàn)檢查
,會及時提示錯誤刨沦;
4.生成中間代碼(Code Generation)
主要功能:CodeGen
負責將AST
語法樹自頂向下遍歷诗宣,逐步翻譯成IR
中間代碼;
IR
中間代碼:
- 這是一種更接近于機器碼的語言想诅,使得編譯器被分為前端和后端召庞,不同的平臺可以利用各自的編譯器將中間代碼岛心,轉(zhuǎn)化為適合不同平臺的機器碼;
- 對于
iOS
系統(tǒng)來說篮灼,IR
中間代碼生成的就是Mach-O
可執(zhí)行文件; -
IR
是前端的輸出忘古,后端的輸入;
七诅诱、匯編(Assembly)
輸出中間代碼
標志著前端工作的完成髓堪,接下來將進入后端的處理流程。
1.LLVM優(yōu)化中間代碼
中間代碼IR
進入后端逢艘,LLVM
會對其進行優(yōu)化:
Optimization Level
bitcode
2.生成匯編代碼
LLVM
對IR
進行優(yōu)化后旦袋,會針對不同架構(gòu)生成不同匯編代碼骤菠;
匯編階段的目的:
- 將代碼匯編化它改,并將符號進行歸類;
- 將外部導入符號商乎,放到重定位符號表央拖;
- 最后生成一個或多個
.o
目標文件;
使用xcrun
命令鹉戚,生成匯編文件:
xcrun clang -S main.m -o main.s
打開.s
文件鲜戒,摘取內(nèi)容如下:
.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
操作已經(jīng)被轉(zhuǎn)化為匯編命令形式的調(diào)用抹凳,即callq _NSLog
遏餐;
3.生成目標文件
該階段是匯編器
將匯編代碼
轉(zhuǎn)換為機器代碼
,并輸出目標文件
赢底,即.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
類型洽损,即目標文件類型:
-
Mach-O
文件是用于iOS
和OS
平臺上的文件類型庞溜; -
Mach-O
作為a.out
格式的替代,提供了更強的擴展性碑定,也提升了符號表中信息的訪問速度流码;
使用xcrun
命令,查看下main.o
中的符號:
xcrun nm -nm main.o
終端顯示效果如下:
(undefined) external _NSLog
(undefined) external ___CFConstantStringClassReference
0000000000000000 (__TEXT,__text) external _main
可以看到延刘,此時我們使用的NSLog
函數(shù)漫试,對應(yīng)著_NSLog
符號:
-
undefined
:表示在當前文件暫時找不到符號_NSLog
; -
external
:表示這個符號是外部可以訪問的访娶,對應(yīng)表示文件私有的符號是non-external
商虐;
八觉阅、鏈接(Linking)
主要功能:符號解析、重定位秘车、合并目標文件典勇,最終生成可執(zhí)行文件;
1.使用xcrun命令執(zhí)行鏈接叮趴,得到可執(zhí)行文件
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, 梧雨北辰
結(jié)果分析:雖然還是Mach-O
格式,但此時已經(jīng)是executable
類型了眯亦,即可執(zhí)行文件伤溉。而且運行該文件后也打印出了預期的結(jié)果;
3.再次使用xcrun命令妻率,查看可執(zhí)行文件的符號表
% 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
結(jié)果分析:_NSLog
符號依然是undefined
乱顾,不過此時多了一些信息,即from Foundation
宫静,表示這個符號來自于Foundation
走净,會在運行時動態(tài)綁定;
4.鏈接階段的主要任務(wù)
1.符號解析
將每個符號引用和對應(yīng)的符號定義關(guān)聯(lián)起來孤里;
- 鏈接器鏈接多文件時會創(chuàng)建符號表伏伯,用于記錄所有已經(jīng)定義和未定義的符號;
- 出現(xiàn)相同符號捌袜,會報錯:
"ld:dumplicate symbols"
说搅; - 在其他目標文件里沒有找到到符號,會報錯:
"Undefined symbols"
虏等;
- 出現(xiàn)相同符號捌袜,會報錯:
- 另外弄唧,鏈接器在整理函數(shù)的符號調(diào)用關(guān)系時,可以幫助我們理清那些函數(shù)沒有被調(diào)用博其,并自動去除掉套才;
2.重定位
將變量名、函數(shù)名這些符號定義與一個內(nèi)存位置關(guān)聯(lián)起來慕淡;
- 因為只有通過了綁定背伴,機器才知道需要操作什么內(nèi)存地址;
- 否則峰髓,我們就需要在寫代碼時給每個指令設(shè)置好內(nèi)存地址傻寂,不僅操作繁瑣,而且容易引起出錯携兵;
3.合并目標文件
將多個.m文件
編譯產(chǎn)生的.o
目標文件與其他Mach-O
文件(如dylib
疾掰、a
、tbd
)徐紧,合成一個Mach-O
格式的可執(zhí)行文件静檬;
- 通常項目都會包含多個文件炭懊,不同文件之間的
變量
和接口函數(shù)
就會產(chǎn)生相互依賴關(guān)系; - 程序運行前拂檩,需要使用鏈接器將多個文件里的符號和地址綁定起來侮腹,才能保證整個程序里的變量、接口的正常調(diào)用稻励;
5.理解靜態(tài)鏈接與動態(tài)鏈接
靜態(tài)鏈接:作用于編譯期父阻,鏈接后的文件依然可能會存在一些"undefined"
的符號。但是這些符號都會被記錄下來望抽,在運行時再通過dlopen
和dlsym
動態(tài)鏈接綁定加矛;
動態(tài)鏈接:作用于運行時,這樣的優(yōu)勢在于:諸多類似UIKit
這樣的共享庫將不必包含在每一個App
包里煤篙。比如:我們使用到的UIKit
系統(tǒng)庫斟览,等到點擊App
真正開始運行之前,才會去鏈接依賴的UIKit
舰蟆,鏈接完成再運行App
趣惠;