在說這篇文章之前,首先我們帶入一個(gè)問題,在Xcode中我們最常使用的一個(gè)組合鍵cmd+b按下之后都進(jìn)行了哪一些工作?偉大的ARC內(nèi)存管理方式又是如何實(shí)現(xiàn)內(nèi)存管理的?
又或者我不了解編譯過程代碼照樣擼得飛起,摸透這晦澀難理解的東西有什么用?
下面要開始啰嗦了
LLVM簡介-來自https://zh.wikipedia.org/wiki/LLVM
LLVM項(xiàng)目的發(fā)展起源于2000年伊利諾伊大學(xué)厄巴納-香檳分校維克拉姆·艾夫(Vikram Adve)與克里斯·拉特納(Chris Lattner)的研究,他們想要為所有靜態(tài)及動(dòng)態(tài)語言創(chuàng)造出動(dòng)態(tài)的編譯技術(shù)。LLVM是以BSD授權(quán)來發(fā)展的開源軟件。2005年,蘋果電腦雇用了克里斯·拉特納及他的團(tuán)隊(duì)為蘋果電腦開發(fā)應(yīng)用程序系統(tǒng),LLVM為現(xiàn)今Mac OS X及iOS開發(fā)工具的一部分。
LLVM的命名最早源自于底層虛擬機(jī)(Low Level Virtual Machine)的首字母縮寫,由于這個(gè)項(xiàng)目的范圍并不局限于創(chuàng)建一個(gè)虛擬機(jī),這個(gè)縮寫導(dǎo)致了廣泛的疑惑。LLVM開始成長之后,成為眾多編譯工具及低級工具技術(shù)的統(tǒng)稱,使得這個(gè)名字變得更不貼切,開發(fā)者因而決定放棄這個(gè)縮寫的意涵,現(xiàn)今LLVM已單純成為一個(gè)品牌,適用于LLVM下的所有項(xiàng)目,包含LLVM中介碼(LLVM IR)、LLVM除錯(cuò)工具、LLVM C++標(biāo)準(zhǔn)庫等。
關(guān)于swift之父加入Apple有個(gè)有趣的故事
Xcode3之前,用的是GCC
Xcode3,GCC仍然保留,但是也推出了LLVM,蘋果推薦LLVM-GCC混合編譯器,但還不是默認(rèn)編譯器
Xcode4,LLVM-GCC成為默認(rèn)編譯器,但GCC仍保留
Xcode4.2,LLVM3.0成為默認(rèn)編譯器,純用GCC不復(fù)可能
Xcode4.6,LLVM升級到4.2版本
Xcode5,LLVM-GCC被遺棄,新的編譯器是LLVM5.0,從GCC過渡到LLVM的時(shí)代正式完成
當(dāng)時(shí)蘋果對Objective-C新增了許多特性,但這時(shí)的Apple使用的是當(dāng)時(shí)一手遮天的GCC作為前端。GCC并不為這些新特性買賬--不給實(shí)現(xiàn),因此索性后來兩者 分成兩條分支分別開發(fā),這也造成Apple的編譯器版本遠(yuǎn)落后于GCC的官方版本。并且GCC的代碼耦合度太高,不好獨(dú)立,而且越是后期的版本,代碼質(zhì)量越差7,但Apple想做的很多功能(比如更好的IDE支持)需要模塊化的方式來調(diào)用GCC,但GCC一直不給做?!禛CC運(yùn)行環(huán)境豁免條款 (英文版)8》從根本上限制了LLVM-GCC的開發(fā)。 所以,這種不和讓Apple一直在尋找一個(gè)高效的、模塊化的、協(xié)議更放松的開源替代品。而UIUC的高材生Chris Lattner的LLVM顯然是一個(gè)很棒的選擇。
Clang - a C language family frontend for LLVM
Clang(發(fā)音為/?kl??/) 是一個(gè)C、C++、Objective-C和Objective-C++編程語言的編譯器前端。它采用了底層虛擬機(jī)(LLVM)作為其后端。它的目標(biāo)是提供一個(gè)GNU編譯器套裝(GCC)的替代品。作者是克里斯·拉特納,在蘋果公司的贊助支持下進(jìn)行開發(fā),而源代碼授權(quán)是使用類BSD的伊利諾伊大學(xué)厄巴納-香檳分校開源碼許可。
Clang項(xiàng)目包括Clang前端和Clang靜態(tài)分析器等。
Clang的在出生之前就已經(jīng)明確了他的使命——干掉該死的GCC。有了LLVM+Clang,從此,蘋果的開發(fā)面貌煥然一新。從此擺脫了GCC的限制??陀^的說GCC是有很多的優(yōu)點(diǎn),例如支持多平臺,很流行,基于C無需C++編譯器即可編譯。這些優(yōu)點(diǎn)到蘋果那就可能是缺點(diǎn)了,蘋果需要的是——快。這正是Clang的優(yōu)點(diǎn),除了快,它還有與GCC兼容,內(nèi)存占用小,診斷信息可讀性強(qiáng),易擴(kuò)展,易于IDE集成等等優(yōu)點(diǎn)。有個(gè)測試數(shù)據(jù):Clang編譯Objective-C代碼時(shí)速度為GCC的3倍。
LLDB
GCC有個(gè)強(qiáng)大的診斷工具——GDB,相對應(yīng)的Clang下糾錯(cuò)工具就是LLDB。對于LLDB大家應(yīng)該都不陌生,它繼承了GDB的優(yōu)點(diǎn),彌補(bǔ)GDB的不足。iOS開發(fā)者從gbd過渡到lldb沒有任何不適應(yīng)感,最直白的原因就是lldb和gdb常用的命令很多都是一樣的,例如常用的po等。
啰嗦到此結(jié)束
iOS編譯過程

Objective-C與swift都采用Clang作為編譯器前端,編譯器前端主要進(jìn)行語法分析,語義分析,生成中間代碼,在這個(gè)過程中,會進(jìn)行類型檢查,如果發(fā)現(xiàn)錯(cuò)誤或者警告會標(biāo)注出來在哪一行。

編譯器后端會進(jìn)行機(jī)器無關(guān)的代碼優(yōu)化,生成機(jī)器語言,并且進(jìn)行機(jī)器相關(guān)的代碼優(yōu)化,根據(jù)不同的系統(tǒng)架構(gòu)生成不同的機(jī)器碼。
C++,Objective C都是編譯語言。編譯語言在執(zhí)行的時(shí)候,必須先通過編譯器生成機(jī)器碼。

如上圖所示,在xcode按下cmd+B之后的工作流程。
預(yù)處理(Pre-process):他的主要工作就是將宏替換,刪除注釋展開頭文件,生成.i文件。
詞法分析 (Lexical Analysis):將代碼切成一個(gè)個(gè) token,比如大小括號,等于號還有字符串等。是計(jì)算機(jī)科學(xué)中將字符序列轉(zhuǎn)換為標(biāo)記序列的過程。
語法分析(Semantic Analysis):驗(yàn)證語法是否正確,然后將所有節(jié)點(diǎn)組成抽象語法樹 AST 。由 Clang 中 Parser 和 Sema 配合完成
靜態(tài)分析(Static Analysis):使用它來表示用于分析源代碼以便自動(dòng)發(fā)現(xiàn)錯(cuò)誤。
中間代碼生成(Code Generation):開始IR中間代碼的生成了,CodeGen 會負(fù)責(zé)將語法樹自頂向下遍歷逐步翻譯成 LLVM IR,IR 是編譯過程的前端的輸出后端的輸入。
優(yōu)化(Optimize):LLVM 會去做些優(yōu)化工作,在 Xcode 的編譯設(shè)置里也可以設(shè)置優(yōu)化級別-01,-03,-0s,還可以寫些自己的 Pass,官方有比較完整的 Pass 教程: Writing an LLVM Pass — LLVM 5 documentation 。如果開啟了 bitcode 蘋果會做進(jìn)一步的優(yōu)化,有新的后端架構(gòu)還是可以用這份優(yōu)化過的 bitcode 去生成。

生成目標(biāo)文件(Assemble):生成Target相關(guān)Object(Mach-o)
鏈接(Link):生成 Executable 可執(zhí)行文件
經(jīng)過這一步步,我們用各種高級語言編寫的代碼就轉(zhuǎn)換成了機(jī)器可以看懂可以執(zhí)行的目標(biāo)代碼了。
環(huán)境搭建
cd /opt
sudo mkdir llvm
sudo chown `whoami` llvm
cd llvm
export LLVM_HOME=`pwd`
git clone -b release_39 git@github.com:llvm-mirror/llvm.git llvm
git clone -b release_39 git@github.com:llvm-mirror/clang.git llvm/tools/clang
git clone -b release_39 git@github.com:llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra
git clone -b release_39 git@github.com:llvm-mirror/compiler-rt.git llvm/projects/compiler-rt
mkdir llvm_build
cd llvm_build
cmake ../llvm -DCMAKE_BUILD_TYPE:STRING=Release
make -j`sysctl -n hw.logicalcpu`
文件很多很大,需要下載一段時(shí)間
Clang Static Analyzer靜態(tài)代碼分析
clang 靜態(tài)分析是通過建立分析引擎和 checkers 所組成的架構(gòu),這部分功能可以通過 clang —analyze 命令方式調(diào)用。
命令行執(zhí)行
通過clang -cc1 -analyzer-checker-help可以列出能調(diào)用的 checker,但這些checker并不是所有都是默認(rèn)開啟的
這里使用一個(gè)默認(rèn)關(guān)閉的checker-alpha.security.ArrayBoundV2作為例子進(jìn)行操作
$ clang -cc1 -analyzer-checker-help
alpha.core.BoolAssignment Warn about assigning non-{0,1} values to Boolean variables
alpha.core.CastSize Check when casting a malloced type T, whether the size is a multiple of the size of T
alpha.core.CastToStruct Check for cast from non-struct pointer to struct pointer
alpha.core.FixedAddr Check for assignment of a fixed address to a pointer
alpha.core.IdenticalExpr Warn about unintended use of identical expressions in operators
alpha.core.PointerArithm Check for pointer arithmetic on locations other than array elements
alpha.core.PointerSub Check for pointer subtractions on two pointers pointing to different memory chunks
alpha.core.SizeofPtr Warn about unintended use of sizeof() on pointer expressions
alpha.cplusplus.NewDeleteLeaks Check for memory leaks. Traces memory managed by new/delete.
alpha.cplusplus.VirtualCall Check virtual function calls during construction or destruction
...
alpha.security.ArrayBound Warn about buffer overflows (older checker)
alpha.security.ArrayBoundV2 Warn about buffer overflows (newer checker)
alpha.security.MallocOverflow Check for overflows in the arguments to malloc()
alpha.security.ReturnPtrRange Check for an out-of-bound pointer being returned to callers
...
core.CallAndMessage Check for logical errors for function calls and Objective-C message expressions (e.g., uninitialized arguments, null function pointers)
core.DivideZero Check for division by zero
core.DynamicTypePropagation Generate dynamic type information
core.NonNullParamChecker Check for null pointers passed as arguments to a function whose arguments are references or marked with the 'nonnull' attribute
core.NullDereference Check for dereferences of null pointers
core.StackAddressEscape Check that addresses to stack memory do not escape the function
...
unix.API Check calls to various UNIX/Posix functions
unix.Malloc Check for memory leaks, double free, and use-after-free problems. Traces memory managed by malloc()/free().
unix.MallocSizeof Check for dubious malloc arguments involving sizeof
unix.MismatchedDeallocator Check for mismatched deallocators.
unix.cstring.BadSizeArg Check the size argument passed into C string functions for common erroneous patterns
unix.cstring.NullArg Check for null pointers being passed as arguments to C string functions
可以使用 -enable-checker 和 -disable-checker 開啟和禁用具體的 checker 或者 某種類別的 checker。
$ scan-build -enable-checker alpha.security.ArrayBoundV2 ... # 啟用數(shù)組邊界檢查
當(dāng)然,使用scan-build啟用的checker只適用于使用scan-build生成的html報(bào)告。
scan-build在編譯安裝 llvm/clang 之后可以在/llvm/tools/clang/tools/scan-build目錄下找到
//允許未被默認(rèn)允許的check并進(jìn)行代碼分析并將輸出結(jié)果輸出至網(wǎng)頁
./scan-build -enable-checker alpha.security.ArrayBoundV2 --use-analyzer=/opt/llvm/llvm_build/bin -V xcodebuild -project /Users/yuhao/TestClang/TestClang.xcodeproj -sdk iphonesimulator10.3
我們在TestClang.xcodeproj的main.m文件中插入一段數(shù)組越界的代碼
int main(){
@autoreleasepool {
int a[2];
int i;
for (i = 0; i < 3; i++){
a[i] = 0;
}
}
return 0;
}
然后執(zhí)行上面的命令,會導(dǎo)出這樣的一個(gè)界面

查看報(bào)表

報(bào)表中提示了該代碼有數(shù)組越界的問題。
Xcode執(zhí)行
Xcode本身已經(jīng)自帶了靜態(tài)檢測的功能,可以通過Product-Analyze來執(zhí)行靜態(tài)檢測,這也只是用自帶的clang去執(zhí)行,如果想用其他的版本,比如自己編譯clang,就需要通過命令來設(shè)置。

在Xcode的Product選項(xiàng)卡下有Analyze的選項(xiàng),Xcode中默認(rèn)提供了一些checkers。
Usage: set-xcode-analyzer [options]
Options:
-h, --help show this help message and exit
--use-checker-build=PATH
Use the Clang located at the provided absolute path,
e.g. /Users/foo/checker-1
--use-xcode-clang Use the Clang bundled with Xcode
可以看到,它有2個(gè)選項(xiàng),
--use-checker-build:用于將xcode的clang版本切換成設(shè)定的版本
--use-xcode-clang:用于將xcode的clang版本切換回去
注:在執(zhí)行上面命令的時(shí)候,需要退出xcode執(zhí)行;且需要用sudo的方式運(yùn)行。
依然使用上面的project文件,在Build Settings添加參數(shù),如圖

-Xanalyzer -analyzer-checker=alpha.security.ArrayBoundV2
然后cmd+shift+b

在Xcode中也出現(xiàn)了和報(bào)表同樣的提示。
關(guān)于checker的開發(fā)可以看這里。
關(guān)于ARC(AUTOMATIC REFERENCE COUNTING)
ARC是ios5.0引入的新特性,完全消除手動(dòng)管理內(nèi)存的繁瑣,編譯器會自動(dòng)在適合的代碼里面插入適當(dāng)?shù)膔etain,release,autorelease的語句。我們不要再擔(dān)心內(nèi)存管理,因?yàn)榫幾g器幫我們做了這一切。
我們都知道ARC的規(guī)則就是只要對象沒有強(qiáng)指針引用,就會被釋放掉。那么,該對象是什么時(shí)候被釋放,又是誰操作去釋放該對象的?
自動(dòng)添加release
int main(int argc, const char * argv[]) {
id a;
return 0;
}
上面的代碼中有強(qiáng)引用的對象,通過以下命令將代碼編譯成中間語言:
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
結(jié)果如下:
define i32 @main(i32, i8**) #0 {
%3 = alloca i32, align 4
%4 = alloca i32, align 4
%5 = alloca i8**, align 8
%6 = alloca i8*, align 8
store i32 0, i32* %3, align 4
store i32 %0, i32* %4, align 4
store i8** %1, i8*** %5, align 8
store i8* null, i8** %6, align 8
store i32 0, i32* %3, align 4
call void @objc_storeStrong(i8** %6, i8* null) #1
%7 = load i32, i32* %3, align 4
ret i32 %7
}
alloca函數(shù)申請內(nèi)存地址,而store表示將值存到指定地址。 函數(shù)的最后調(diào)用了函數(shù)objc_storeStrong,查詢ARC文檔可以知道objc_storeStrong的實(shí)現(xiàn)。
void objc_storeStrong(id *object, id value) {
id oldValue = *object;
value = [value retain];
*object = value;
[oldValue release];
}
call void @objc_storeStrong(i8** %6, i8* null)對null進(jìn)行了retain,對a進(jìn)行了release。
綜上,在__strong類型的變量的作用域結(jié)束時(shí),自動(dòng)添加release函數(shù)進(jìn)行釋放。
自動(dòng)添加retain
查閱ARC文檔,發(fā)現(xiàn)有objc_retain這樣一個(gè)函數(shù),顧名思義,該函數(shù)就是將對象進(jìn)行retain操作。
id objc_retainAutorelease(id value) {
return objc_autorelease(objc_retain(value));
}
objc_retainAutorelease(id value)當(dāng)value為null或指針指向有效對象,如果value為null,則此調(diào)用不起作用。否則,它執(zhí)行保留操作,然后執(zhí)行自動(dòng)釋放操作。即對一個(gè)變量先進(jìn)行一次retain,再添進(jìn)行autorelease。
weak的實(shí)現(xiàn)
runtime是如何實(shí)現(xiàn)在weak修飾的變量的對象在被銷毀時(shí)自動(dòng)置為nil的呢?一個(gè)普遍的解釋是:runtime對注冊的類會進(jìn)行布局,對于weak修飾的對象會放入一個(gè)hash表中。用weak指向的對象內(nèi)存地址作為key,當(dāng)此對象的引用計(jì)數(shù)為0的時(shí)候會dealloc,假如weak指向的對象內(nèi)存地址是a,那么就會以a為鍵在這個(gè)weak表中搜索,找到所有以a為鍵的weak對象,從而設(shè)置為nil。
weak指針的實(shí)現(xiàn)借助Objective-C的運(yùn)行時(shí)特性,runtime通過 objc_storeWeak, objc_destroyWeak和 objc_moveWeak等方法,直接修改__weak對象,來實(shí)現(xiàn)弱引用。
objc_storeWeak函數(shù),將附有__weak標(biāo)識符的變量的地址注冊到weak表中,weak表是一份與引用計(jì)數(shù)表相似的散列表。
而該變量會在釋放的過程中清理weak表中的引用,變量釋放調(diào)用以下函數(shù):
dealloc
_objec_rootDealloc
object_dispose
objc_destructInstance
objc_clear_deallocating
在最后的objc_clear_deallocating函數(shù)中,從weak表中找到弱引用指針的地址,然后置為nil,并從weak表刪除記錄。
關(guān)于ARC更多實(shí)現(xiàn)請參閱探究ARC