前言
两年前曾经写过一篇关于编译的文章《iOS编译过程的原理和应用》,这篇文章介绍了iOS编译相关基础知识和简单应用,但也很有多问题都没有解释清楚:
Clang和LLVM究竟是什么
源文件到机器码的细节
Linker做了哪些工作
编译顺序如何确定
头文件是什么?XCode是如何找到头文件的?
Clang Module
签名是什么?为什么要签名
为了搞清楚这些问题,我们来挖掘下XCode编译iOS应用的细节。
编译器
把一种编程语言(原始语言)转换为另一种编程语言(目标语言)的程序叫做编译器。
大多数编译器由两部分组成:前端和后端。
前端负责词法分析,语法分析,生成中间代码;
后端以中间代码作为输入,进行行架构无关的代码优化,接着针对不同架构生成不同的机器码。
前后端依赖统一格式的中间代码(IR),使得前后端可以独立的变化。新增一门语言只需要修改前端,而新增一个CPU架构只需要修改后端即可。
Objective C/C/C++使用的编译器前端是clang,swift是swift,后端都是LLVM。
LLVM
LLVM(Low Level Virtual Machine)是一个强大的编译器开发工具套件,听起来像是虚拟机,但实际上LLVM和传统意义的虚拟机关系不大,只不过项目最初的名字是LLVM罢了。
LLVM的核心库提供了现代化的source-target-independent优化器和支持诸多流行CPU架构的代码生成器,这些核心代码是围绕着LLVM IR(中间代码)建立的。
基于LLVM,又衍生出了一些强大的子项目,其中iOS开发者耳熟能详的是:Clang和LLDB。
clang
clang是C语言家族的编译器前端,诞生之初是为了替代GCC,提供更快的编译速度。一张图了解clang编译的大致流程:
接下来,从代码层面看一下具体的转化过程,新建一个main.c:
#include
// 一点注释
#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
int 'int' [StartOfLine]Loc=<4:1>4:1>
identifier 'main' [LeadingSpace]Loc=<4:5>4:5>
....
Loc=<1:1>标示这个token位于源文件main.c的第1行,从第1个字符开始。保存token在源文件中的位置是方便后续clang分析的时候能够找到出错的原始位置。1:1>
语法分析(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;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;34m |-�[0m�[0;1;35mCallExpr�[0m�[0;33m 0x7fcc188dc880�[0m �[0;32m'int'�[0m�[0;36m�[0m�[0;36m�[0m
�[0;34m | |-�[0m�[0;1;35mImplicitCastExpr�[0m�[0;33m 0x7fcc188dc868�[0m �[0;32m'int (*)(const char *, ...)'�[0m�[0;36m�[0m�[0;36m�[0m
�[0;34m | | `-�[0m�[0;1;35mDeclRefExpr�[0m�[0;33m 0x7fcc188dc7a0�[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;32m'const char *'�[0m�[0;36m�[0m�[0;36m�[0m
�[0;34m | `-�[0m�[0;1;35mImplicitCastExpr�[0m�[0;33m 0x7fcc188dc8b0�[0m �[0;32m'char *'�[0m�[0;36m�[0m�[0;36m�[0m
�[0;34m | `-�[0m�[0;1;35mStringLiteral�[0m�[0;33m 0x7fcc188dc808�[0m �[0;32m'char [13]'�[0m�[0;36m lvalue�[0m�[0;36m�