编译器的原理浅析
程序员文章站
2023-12-25 12:50:21
...
一.编译器介绍
1.对于iOS的编译器来说,就是将Objective-C转化为更为低级的语言(即机器语言,转为可执行文件),以及对代码的分析,确保没有明显的错误
2.Xcode的默认编译器是Clang/LLVM,其中clang主要作为编译前端,而LLVM主要作为编译器后端
二.编译器是将源代码编译成可执行文件的步骤
可以使用clang命令看到编译器的处理过程:
clang -ccc-print-phases main.m
0: input, “main.m”, objective-c
1: preprocessor, {0}, objective-c-cpp-output
2: compiler, {1}, ir
3: backend, {2}, assembler
4: assembler, {3}, object
5: linker, {4}, image
6: bind-arch, “x86_64”, {5}, image
1.预处理指令
1).主要是处理#开头的预处理指令,包括import头文件、宏展开、条件预处理指令、
删除注释、添加行号和文件名标识。
可以通过 $clang -E main.m 查看替换的情况
2).pch文件可以加快库的编译时间
3).引入了模块 - modules功能,这使预处理变得更加的高级。当引入苹果自己的库时,可以使用@import关键字引入,
告诉编译器使用modules的引用形式,苹果已经将这些库进行了封装,生成了一个已经编译的modules文件列表,编译时
首先会去已编译的列表中查找,如果存在就直接使用,没有再进行编译
2.Lexical Analysis - 词法分析
预处理之后,每一个.m文件都有一堆的声明和定义,将这些代码文本从string转化为特殊的标记流,
也就说拆分成一个个的tokens。
比如下面的一段代码:
int main() {
NSLog(@"hello, %@", @"world");
return 0;
}
使用 $clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m 将上面的代码的标记流导出
-dump-tokens:作用是透传给编译器前端,将tokens打印出来
每一个标记都包含了对应的源码内容和其在源码中的位置,如果编译中遇到什么错误,clang 能够在源码中指出出错的具体位置
3.Semantic Analysis - 语法分析
语法分析由Clang 中 Parser 和 Sema 配合完成
1).验证语法是否正确
2).提示各种错误警告提示
3).根据设置语言的语法,形成语义结点,并将所有节点组合形成抽象语法树AST
$clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
生成抽象的语法树
在抽象语法树中的每一个节点都标注了其对应源码中的位置,如果产生了什么问题,clang可以定位到问题所在处的源码位置。
4).AST语法树相关的关键字
a.TranslationUnitDecl 是根节点,表示一个源文件。Decl表示一个声明,Expr表示表达式,Literal表示字面是特殊的Expr,Stmt表示语句
ObjCUnusedIVarsChecker
Static Analysis - 静态分析
一旦编译器把源码生成了抽象语法树,编译器可以对这棵树做分析处理,找出代码中的错误。
1).类型检查
最为常见的是是否发送正确的消息给正确的对象,是否在正确的值上调用了正确的函数。
2).其他分析
clang源码中进入目录lib/StaticAnalyzer/Checkers,包含了所有静态检查内容
a.ObjCUnusedIVarsChecker.cpp是用来检查是否有定义了但是没有使用过的变量
b.ObjCSelfInitChecker.cpp 检查在你的初始化方法中调用self之前,
是否已经调用了[self initWith...]或[super init]
3).clang静态分析是通过分析引擎和checkers所组成的架构。
a.clang static analyzer分为analyzer core 分析引擎和checkers两部分,
所有的checker都是基于底层分析引擎之上,通过分析引擎提供的功能能够编写新的checker.
b.$clang -cc1 -analyzer-checker-help可以列出能调用的checker。
c.整个静态分析clang static analyzer的入口在lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
4.CodeGen - IR 代码生成
1).语法树从上至下遍历,翻译成LLVM中间代码,作为前后端的桥接语言,是Clang 编译器前端的输出,LLVM 编译器后端的输入。
2).与runtime桥接
2.1).内存结构的生成
a.Class/Meta Class/Protocol/Category 生成并存放在指定section中,_DATA 或 _objc_classrefs
b.Method/Ivar/Property 生成
c.组成method_list/ivar_list/property_list 并填入Class
2.2).为每个 Ivar 合成偏移值常量,其地址为对象的基地址 + 偏移量
2.3).将语法树中的ObjCMessageExpr翻译成相应objc_msgSend,对super关键字的调用翻译成objc_msgSendSuper
2.4).根据修饰符strong/weak/copy/atomic 合成@property,自动实现setter/getter,处理@synthesize
2.5).生成block_layout数据结构
a.变量的capture _block _weak
b.生成_block_invoke 函数
2.6).分析对象引用关系,插入ARC代码,将objc-storeStrong/objc-storeWeak等ARC代码插入
a.自动调用[super dealloc]
b.为每个拥有ivar 的 Class 合成 .cxx_destructor 方法来自动释放类的成员变量自动释放池的管理,
将ObjcAutoreleasePoolStmt 转译成 objc_autoreleasePoolPush/Pop
$clang -S -fobjc-arc -emit-llvm main.m -o main.ll
生成中间代码
5.Optimize - 优化
1).$clang -S -fobjc-arc -emit-llvm main.m -o main.ll
2).$clang -O3 -S -fobjc-arc -emit-llvm main.m -o main.ll
可采用不同优化级别优化中间代码
3).可以再Xcode中设置优化级别
LLVM Bitcode - 生成字节码
字节码是一种包含执行程序、由一序列 op 代码/数据对组成的二进制文件,但与特定机器码无关,需要直译器转译后才能生成机器码,
可以看作是包含一个执行程序的二进制文件
$clang -emit-llvm -c main.m -o main.bc
形成二进制流
6.Assemble - 生成 Target 相关汇编
1).$clang -S -fobjc-arc main.m -o main.s
生成汇编代码
$clang -fmodules -c main.m -o main.o
Mach-o 是苹果系统的目标文件
2).可以在MachOView中查看.o目标文件,其中包含
a.Mach64 Header头部:标记一些元信息,比如架构、CPU、大小端等信息。
b.Load Commands加载命令:表示如何加载每个段的信息。
c.Section区域:多个Segment及Section包含每个段自身的信息,包括数据、代码等.
__TEXT表示只读数据,只读常量,C strings
__DATA表示全局/静态变量
d.Relocations重要定位信息:
e.Symbols符号表
f.string字符串表
3).也可以通过Xcode的link Map File查看二进制文件的布局,在编译之后可以在
/Library/Developer/Xcode/DerivedData/Build_demo-bzowijmyxpepxzfhfxchuovmtumr/Build/Intermediates.noindex/Build_demo.build/Debug-iphonesimulator/Build_demo.build
目录下看到对应的Build_demo-LinkMap-normal-x86_64文件
a.Setions这个区域提供了各个段(segment)和(Section)在可执行文件中的位置和大小,这个区域完整地描述了可执行文件中的全部内容
b.setions分为两种__TEXT代码段和__DATA数据段
c.Symbols对Section中方的各个段进行了二级划分,从中我们可以看出OC方法的存储方式
d.对于
0x100004F80 0x00000010 __DATA __objc_ivar
这个ivar在对应的symbols的区域是
# Symbols:
# Address Size File Name
0x100004F80 0x00000008 [ 6] _OBJC_IVAR_$_AppDelegate._persistentContainer
e.对于string,会显示存储到数据段中
0x100003CB4 0x0000000D [ 5] literal string: hello world
7.Link - 链接,生成 Executable 可执行文件
$clang main.m -o main