前言
两年前曾经写过一篇关于编译的文章《iOS编译过程的原理和应用》,这篇文章介绍了iOS编译相关基础知识和简单应用,但也很有多问题都没有解释清楚:
- Clang和LLVM究竟是什么
- 源文件到机器码的细节
- Linker做了哪些工作
- 编译顺序如何确定
- 头文件是什么?XCode是如何找到头文件的?
- Clang Module
- 签名是什么?为什么要签名
为了搞清楚这些问题,我们来挖掘下XCode编译iOS应用的细节。
编译器
把一种编程语言(原始语言)转换为另一种编程语言(目标语言)的程序叫做编译器。
大多数编译器由两部分组成:前端和后端。
- 前端负责词法分析,语法分析,生成中间代码;
- 后端以中间代码作为输入,进行行架构无关的代码优化,接着针对不同架构生成不同的机器码。
前后端依赖统一格式的中间代码(IR),使得前后端可以独立的变化。新增一门语言只需要修改前端,而新增一个CPU架构只需要修改后端即可。
Objective C/C/C++使用的编译器前端是clang,swift是swift,后端都是LLVM。
![](https://i-blog.csdnimg.cn/blog_migrate/94998226bcf91c2c6bef004f4f952afb.png)
LLVM
LLVM(Low Level Virtual Machine)是一个强大的编译器开发工具套件,听起来像是虚拟机,但实际上LLVM和传统意义的虚拟机关系不大,只不过项目最初的名字是LLVM罢了。
LLVM的核心库提供了现代化的source-target-independent优化器和支持诸多流行CPU架构的代码生成器,这些核心代码是围绕着LLVM IR(中间代码)建立的。
基于LLVM,又衍生出了一些强大的子项目,其中iOS开发者耳熟能详的是:Clang和LLDB。
clang
clang是C语言家族的编译器前端,诞生之初是为了替代GCC,提供更快的编译速度。一张图了解clang编译的大致流程:
![](https://i-blog.csdnimg.cn/blog_migrate/92db3b80f1b4e7bc2463e71fa509eb81.png)
接下来,从代码层面看一下具体的转化过程,新建一个main.c:
#include <stdio.h>
// 一点注释
#define DEBUG 1
int main() {
#ifdef DEBUG
printf("hello debug\n");
#else
printf("hello world\n");
#endif
return 0;
}
预处理(preprocessor)
预处理会替进行头文件引入,宏替换,注释处理,条件编译(#ifdef)等操作。
#include "stdio.h"
就是告诉预处理器将这一行替换成头文件stdio.h
中的内容,这个过程是递归的:因为stdio.h
也有可能包含其头文件。
用clang查看预处理的结果:
xcrun clang -E main.c
预处理后的文件有400多行,在文件的末尾,可以找到main函数
int main() {
printf("hello debug\n");
return 0;
}
可以看到,在预处理的时候,注释被删除,条件编译被处理。
词法分析(lexical anaysis)
词法分析器读入源文件的字符流,将他们组织称有意义的词素(lexeme)序列,对于每个词素,此法分析器产生词法单元(token)作为输出。
$ xcrun clang -fmodules -fsyntax-only -Xclang -dump-tokens main.c
输出:
annot_module_include '#include <s' Loc=<main.c:1:1>
int 'int' [StartOfLine] Loc=<main.c:4:1>
identifier 'main' [LeadingSpace] Loc=<main.c:4:5>
....
Loc=<main.c:1:1>
标示这个token位于源文件main.c的第1行,从第1个字符开始。保存token在源文件中的位置是方便后续clang分析的时候能够找到出错的原始位置。
语法分析(semantic analysis)
词法分析的Token流会被解析成一颗抽象语法树(abstract syntax tree - AST)。
$ xcrun clang -fsyntax-only -Xclang -ast-dump main.c | open -f
main函数AST的结构如下:
[0;34m`-[0m[0;1;32mFunctionDecl[0m[0;33m 0x7fcc188dc700[0m <[0;33mmain.c:4:1[0m, [0;33mline:11:1[0m> [0;33mline:4:5[0m[0;1;36m main[0m [0;32m'int ()'[0m
[0;34m `-[0m[0;1;35mCompoundStmt[0m[0;33m 0x7fcc188dc918[0m <[0;33mcol:12[0m, [0;33mline:11:1[0m>
[0;34m |-[0m[0;1;35mCallExpr[0m[0;33m 0x7fcc188dc880[0m <[0;33mline:6:3[0m, [0;33mcol:25[0m> [0;32m'int'[0m[0;36m[0m[0;36m[0m
[0;34m | |-[0m[0;1;35mImplicitCastExpr[0m[0;33m 0x7fcc188dc868[0m <[0;33mcol:3[0m> [0;32m'int (*)(const char *, ...)'[0m[0;36m[0m[0;36m[0m <[0;31mFunctionToPointerDecay[0m>
[0;34m | | `-[0m[0;1;35mDeclRefExpr[0m[0;33m 0x7fcc188dc7a0[0m <[0;33mcol:3[0m> [0;32m'int (const char *, ...)'[0m[0;36m[0m[0;36m[0m [0;1;32mFunction[0m[0;33m 0x7fcc188c5160[0m[0;1;36m 'printf'[0m [0;32m'int (const char *, ...)'[0m
[0;34m | `-[0m[0;1;35mImplicitCastExpr[0m[0;33m 0x7fcc188dc8c8[0m <[0;33mcol:10[0m> [0;32m'const char *'[0m[0;36m[0m[0;36m[0m <[0;31mBitCast[0m>
[0;34m | `-[0m[0;1;35mImplicitCastExpr[0m[0;33m 0x7fcc188dc8b0[0m <[0;33mcol:10[0m> [0;32m'char *'[0m[0;36m[0m[0;36m[0m <[0;31mArrayToPoin