1.Preprocess - 预处理
- import 头⽂件
- macro 展开
- 处理 ‘#’ 打头的预处理指令,如 #if
$clang -E main.m
将main.m进行预处理
文件 main.m
#import <Foundation/Foundation.h>
int main() {
@autoreleasepool {
id obj = [NSObject new];
NSLog(@"Hello world: %@", obj);
}
return 0;
}
预处理后
...
...
...
# 181 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX10.12.sdk/System/Library/Frameworks/Foundation.framework/Headers/
Foundation.h" 2 3
# 1 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX10.12.sdk/System/Library/Frameworks/Foundation.framework/Headers/
FoundationLegacySwiftCompatibility.h" 1 3
# 185 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/
SDKs/MacOSX10.12.sdk/System/Library/Frameworks/Foundation.framework/Headers/
Foundation.h" 2 3
# 6 "main.m" 2
int main() {
@autoreleasepool {
id obj = [NSObject new];
NSLog(@"Hello world: %@", obj);
}
return 0;
}
2. 词法分析 和 语法分析
- 2.1词法分析
将预处理过的代码⽂本转化成 Token 流
clang命令$clang -fmodules -fsyntax-only -Xclang -dump-tokens main.m
输出:
int 'int' [StartOfLine] Loc=<main.m:7:1>
identifier 'main' [LeadingSpace] Loc=<main.m:7:5>
l_paren '(' Loc=<main.m:7:9>
r_paren ')' Loc=<main.m:7:10>
l_brace '{' [LeadingSpace] Loc=<main.m:7:12>
at '@' [StartOfLine] [LeadingSpace] Loc=<main.m:8:5>
identifier 'autoreleasepool' Loc=<main.m:8:6>
l_brace '{' [LeadingSpace] Loc=<main.m:8:22>
identifier 'id' [StartOfLine] [LeadingSpace] Loc=<main.m:9:9>
identifier 'obj' [LeadingSpace] Loc=<main.m:9:12>
equal '=' [LeadingSpace] Loc=<main.m:9:16>
l_square '[' [LeadingSpace] Loc=<main.m:9:18>
identifier 'NSObject' Loc=<main.m:9:19>
identifier 'new' [LeadingSpace] Loc=<main.m:9:28>
r_square ']' Loc=<main.m:9:31>
semi ';' Loc=<main.m:9:32>
···
源代码程序被输入到扫描器,扫描器运用类似“有限状态机”的算法将源代码的字符序列分割成一系列的记号(Token)。
同时扫描器也会完成其他工作,比如讲标识符放到符号表,将数字、字符串常量存放到文字表等,以备后续使用
- 2.2语法分析
语法分析,在 Clang 中由 Parser 和 Sema 两个模块配合完
成,验证语法是否正确。
语法分析将由扫描器产生的记号进行语法分析,产生语法树。语法分析器生成的语法树是以表达式为节点的树,一个语句是一个表达式,复杂的语句是很多表达式的组合。
clang命令clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
3.语义分析
词法分析仅仅完成了对表达式语法层面的分析,但是它并不了解这个语句是否真正有意义。比如C语言里面两个指针做乘法运行时是没有意义的,但这在语法上是合法的。
这个阶段的语义分析是:静态语义
编译阶段分析语义是静态语言,运行期才能确定的语义是动态语义
语义分析还包括:声明和类型的匹配、类型的转换
4. CodeGen - IR 代码⽣成
CodeGen 负责将语法树从顶⾄下遍历,翻译成 LLVM IR
LLVM IR 是 Frontend 的输出,也是 LLVM Backend 的输
⼊,前后端的桥接语⾔
为什么需要先编译成IR中间代码?
中间代码使得编译器可以分为前端和后端,编译器前端负责产生机器无关的中间代码,编译器后端将中间代码转换成目标机器代码。
这样对于一些可以跨平台的编译器而言,他们可以针对不同的平台使用同一个前端和针对不同的机器平台的数个后端
在OC编译中,IR 代码做了的事:
-
Class / Meta Class / Protocol / Category
内存结构⽣成,
并存放在指定section
中 (如 `Class:_DATA,
_objc_classrefs) -
Method / Ivar / Property
内存结构⽣成 - 组成
method_list / ivar_list / property_list
并填⼊ Class - Non-Fragile ABI:为每个 Ivar 合成 OBJC_IVAR_$_ 偏移
值常量 - 存取 Ivar 的语句 (
_ivar = 123; int a = _ivar;
) 转写成 base + OBJC_IVAR_$_ 的形式 - 将语法树中的
ObjCMessageExpr
翻译成相应版本的
objc_msgSend
,对super
关键字的调⽤翻译成
objc_msgSendSuper
- 根据修饰符
strong / weak / copy / atomic
合成 @property
⾃动实现的setter / getter
- 处理 @synthesize
- ⽣成
block_layout
的数据结构 - 变量的
capture (__block / __weak)
- ⽣成
_block_invoke
函数 - ARC:分析对象引⽤关系,将
objc_storeStrong/ objc_storeWeak
等 ARC 代码插⼊ - 将 ObjCAutoreleasePoolStmt 转译成
objc_autoreleasePoolPush/Pop
- 实现⾃动调⽤
[super dealloc]
- 为每个拥有
ivar
的 Class 合成.cxx_destructor
⽅法来⾃动
释放类的成员变量,代替 MRC 时代的 "self.xxx = nil"
clang命令$clang -S -fobjc-arc -emit-llvm main.m -o main.ll
5.Optimize - 优化 IR
clang命令$clang -O3 -S -fobjc-arc -emit-llvm main.m -o main.ll
6.LLVM Bitcode - ⽣成字节码
clang命令$clang -emit-llvm -c main.m -o main.bc
7.最后:Link ⽣成 Executable
$clang main.m -o main
$./main
参考链接
参考书籍
《程序员的自我修养—链接、装载与库》
网友评论