在说这篇文章以前,首先咱们带入一个问题,在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