在說這篇文章以前,首先咱們帶入一個問題,在Xcode中咱們最常使用的一個組合鍵cmd+b
按下以後都進行了哪一些工做?偉大的ARC內存管理方式又是如何實現內存管理的?html
又或者我不瞭解編譯過程代碼照樣擼得飛起,摸透這晦澀難理解的東西有什麼用?前端
LLVM項目的發展起源於2000年伊利諾伊大學厄巴納-香檳分校維克拉姆·艾夫(Vikram Adve)與克里斯·拉特納(Chris Lattner)的研究,他們想要爲全部靜態及動態語言創造出動態的編譯技術。LLVM是以BSD受權來發展的開源軟件。2005年,蘋果電腦僱用了克里斯·拉特納及他的團隊爲蘋果電腦開發應用程序系統,LLVM爲現今Mac OS X及iOS開發工具的一部分。ios
LLVM的命名最先源自於底層虛擬機(Low Level Virtual Machine)的首字母縮寫,因爲這個項目的範圍並不侷限於建立一個虛擬機,這個縮寫致使了普遍的疑惑。LLVM開始成長以後,成爲衆多編譯工具及低級工具技術的統稱,使得這個名字變得更不貼切,開發者於是決定放棄這個縮寫的意涵,現今LLVM已單純成爲一個品牌,適用於LLVM下的全部項目,包含LLVM中介碼(LLVM IR)、LLVM除錯工具、LLVM C++標準庫等。git
Xcode3以前,用的是GCC
Xcode3,GCC仍然保留,可是也推出了LLVM,蘋果推薦LLVM-GCC混合編譯器,但還不是默認編譯器
Xcode4,LLVM-GCC成爲默認編譯器,但GCC仍保留
Xcode4.2,LLVM3.0成爲默認編譯器,純用GCC不復可能
Xcode4.6,LLVM升級到4.2版本
Xcode5,LLVM-GCC被遺棄,新的編譯器是LLVM5.0,從GCC過渡到LLVM的時代正式完成
複製代碼
當時蘋果對Objective-C新增了許多特性,但這時的Apple使用的是當時一手遮天的GCC做爲前端。GCC並不爲這些新特性買帳--不給實現,所以索性後來二者 分紅兩條分支分別開發,這也形成Apple的編譯器版本遠落後於GCC的官方版本。而且GCC的代碼耦合度過高,很差獨立,並且越是後期的版本,代碼質量越差7,但Apple想作的不少功能(好比更好的IDE支持)須要模塊化的方式來調用GCC,但GCC一直不給作。《GCC運行環境豁免條款 (英文版)8》從根本上限制了LLVM-GCC的開發。 因此,這種不和讓Apple一直在尋找一個高效的、模塊化的、協議更放鬆的開源替代品。而UIUC的高材生Chris Lattner的LLVM顯然是一個很棒的選擇。github
Clang(發音爲/ˈklæŋ/) 是一個C、C++、Objective-C和Objective-C++編程語言的編譯器前端。它採用了底層虛擬機(LLVM)做爲其後端。它的目標是提供一個GNU編譯器套裝(GCC)的替代品。做者是克里斯·拉特納,在蘋果公司的贊助支持下進行開發,而源代碼受權是使用類BSD的伊利諾伊大學厄巴納-香檳分校開源碼許可。 Clang項目包括Clang前端和Clang靜態分析器等。express
Clang的在出生以前就已經明確了他的使命——幹掉該死的GCC。有了LLVM+Clang,今後,蘋果的開發面貌面目一新。今後擺脫了GCC的限制。客觀的說GCC是有不少的優勢,例如支持多平臺,很流行,基於C無需C++編譯器便可編譯。這些優勢到蘋果那就多是缺點了,蘋果須要的是——快。這正是Clang的優勢,除了快,它還有與GCC兼容,內存佔用小,診斷信息可讀性強,易擴展,易於IDE集成等等優勢。有個測試數據:Clang編譯Objective-C代碼時速度爲GCC的3倍。編程
GCC有個強大的診斷工具——GDB,相對應的Clang下糾錯工具就是LLDB。對於LLDB你們應該都不陌生,它繼承了GDB的優勢,彌補GDB的不足。iOS開發者從gbd過渡到lldb沒有任何不適應感,最直白的緣由就是lldb和gdb經常使用的命令不少都是同樣的,例如經常使用的po等。swift
Objective-C與swift都採用Clang做爲編譯器前端,編譯器前端主要進行語法分析,語義分析,生成中間代碼,在這個過程當中,會進行類型檢查,若是發現錯誤或者警告會標註出來在哪一行。後端
編譯器後端會進行機器無關的代碼優化,生成機器語言,而且進行機器相關的代碼優化,根據不一樣的系統架構生成不一樣的機器碼。數組
C++,Objective C都是編譯語言。編譯語言在執行的時候,必須先經過編譯器生成機器碼。
如上圖所示,在xcode按下cmd+B以後的工做流程。
預處理(Pre-process):他的主要工做就是將宏替換,刪除註釋展開頭文件,生成.i文件。 詞法分析 (Lexical Analysis):將代碼切成一個個 token,好比大小括號,等於號還有字符串等。是計算機科學中將字符序列轉換爲標記序列的過程。 語法分析(Semantic Analysis):驗證語法是否正確,而後將全部節點組成抽象語法樹 AST 。由 Clang 中 Parser 和 Sema 配合完成 靜態分析(Static Analysis):使用它來表示用於分析源代碼以便自動發現錯誤。 中間代碼生成(Code Generation):開始IR中間代碼的生成了,CodeGen 會負責將語法樹自頂向下遍歷逐步翻譯成 LLVM IR,IR 是編譯過程的前端的輸出後端的輸入。 優化(Optimize):LLVM 會去作些優化工做,在 Xcode 的編譯設置裏也能夠設置優化級別-01,-03,-0s,還能夠寫些本身的 Pass,官方有比較完整的 Pass 教程: Writing an LLVM Pass — LLVM 5 documentation 。若是開啓了 bitcode 蘋果會作進一步的優化,有新的後端架構仍是能夠用這份優化過的 bitcode 去生成。
生成目標文件(Assemble):生成Target相關Object(Mach-o) 連接(Link):生成 Executable 可執行文件
通過這一步步,咱們用各類高級語言編寫的代碼就轉換成了機器能夠看懂能夠執行的目標代碼了。
環境搭建
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`
複製代碼
文件不少很大,須要下載一段時間
###Clang Static Analyzer靜態代碼分析
clang 靜態分析是經過創建分析引擎和 checkers 所組成的架構,這部分功能能夠經過 clang —analyze 命令方式調用。
####命令行執行 經過clang -cc1 -analyzer-checker-help
能夠列出能調用的 checker,但這些checker並非全部都是默認開啓的
這裏使用一個默認關閉的checker-alpha.security.ArrayBoundV2做爲例子進行操做
複製代碼
$ 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 ... # 啓用數組邊界檢查
複製代碼
固然,使用scan-build
啓用的checker
只適用於使用scan-build
生成的html報告。 scan-build
在編譯安裝 llvm/clang 以後能夠在/llvm/tools/clang/tools/scan-build
目錄下找到
//容許未被默認容許的check並進行代碼分析並將輸出結果輸出至網頁
./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文件中插入一段數組越界的代碼
int main(){
@autoreleasepool {
int a[2];
int i;
for (i = 0; i < 3; i++){
a[i] = 0;
}
}
return 0;
}
複製代碼
而後執行上面的命令,會導出這樣的一個界面
查看報表
報表中提示了該代碼有數組越界的問題。
####Xcode執行 Xcode自己已經自帶了靜態檢測的功能,能夠經過Product-Analyze來執行靜態檢測,這也只是用自帶的clang去執行,若是想用其餘的版本,好比本身編譯clang,就須要經過命令來設置。
在Xcode的Product選項卡下有Analyze的選項,Xcode中默認提供了一些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個選項,
--use-checker-build
:用於將xcode的clang版本切換成設定的版本 --use-xcode-clang
:用於將xcode的clang版本切換回去
注:在執行上面命令的時候,須要退出xcode執行;且須要用sudo的方式運行。
依然使用上面的project文件,在Build Settings添加參數,如圖
-Xanalyzer -analyzer-checker=alpha.security.ArrayBoundV2
複製代碼
而後cmd+shift+b
在Xcode中也出現了和報表一樣的提示。 關於checker的開發能夠看這裏。
###關於ARC(AUTOMATIC REFERENCE COUNTING)
ARC是ios5.0引入的新特性,徹底消除手動管理內存的繁瑣,編譯器會自動在適合的代碼裏面插入適當的retain,release,autorelease的語句。咱們不要再擔憂內存管理,由於編譯器幫咱們作了這一切。 咱們都知道ARC的規則就是隻要對象沒有強指針引用,就會被釋放掉。那麼,該對象是何時被釋放,又是誰操做去釋放該對象的?
自動添加release
int main(int argc, const char * argv[]) {
id a;
return 0;
}
複製代碼
上面的代碼中有強引用的對象,經過如下命令將代碼編譯成中間語言:
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
複製代碼
結果以下:
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函數申請內存地址,而store表示將值存到指定地址。 函數的最後調用了函數objc_storeStrong
,查詢ARC文檔能夠知道objc_storeStrong
的實現。
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
進行了retain
,對a
進行了release
。 綜上,在__strong
類型的變量的做用域結束時,自動添加release
函數進行釋放。
自動添加retain 查閱ARC文檔,發現有objc_retain
這樣一個函數,顧名思義,該函數就是將對象進行retain
操做。
id objc_retainAutorelease(id value) {
return objc_autorelease(objc_retain(value));
}
複製代碼
objc_retainAutorelease(id value)
當value
爲null
或指針指向有效對象,若是value
爲null
,則此調用不起做用。不然,它執行保留操做,而後執行自動釋放操做。即對一個變量先進行一次retain
,再添進行autorelease
。
weak的實現 runtime
是如何實如今weak
修飾的變量的對象在被銷燬時自動置爲nil
的呢?一個廣泛的解釋是:runtime
對註冊的類會進行佈局,對於weak
修飾的對象會放入一個hash
表中。用weak
指向的對象內存地址做爲key
,當此對象的引用計數爲0
的時候會dealloc
,假如weak
指向的對象內存地址是a
,那麼就會以a
爲鍵在這個weak
表中搜索,找到全部以a
爲鍵的weak
對象,從而設置爲nil
。
weak
指針的實現藉助Objective-C
的運行時特性,runtime
經過 objc_storeWeak
, objc_destroyWeak
和 objc_moveWeak
等方法,直接修改__weak
對象,來實現弱引用。
objc_storeWeak
函數,將附有__weak
標識符的變量的地址註冊到weak
表中,weak
表是一份與引用計數表類似的散列表。
而該變量會在釋放的過程當中清理weak
表中的引用,變量釋放調用如下函數:
dealloc
_objec_rootDealloc
object_dispose
objc_destructInstance
objc_clear_deallocating
複製代碼
在最後的objc_clear_deallocating
函數中,從weak
表中找到弱引用指針的地址,而後置爲nil
,並從weak
表刪除記錄。
關於ARC更多實現請參閱探究ARC