前面两篇文章都有讲到编译与链接,但是个人感觉不交散,这里总结一下:
大家在探索整个流程的时候,尽量不要引入外界的库,这样编译起来会比较顺畅。
另外可以通过clang -help
来查看clang
的相关指令。
我们现在有一个命令行工程:
int main(int argc, const char * argv[]) {
return 0;
}
我们平时都会用cmd + B
来编译工程;那么这么一步操作究竟做了些什么呢?
- 这个工程中有四步隐藏的操作:
预处理
、编译
、汇编
和链接
。
预处理
预处理
也被称作预编译(Prepressing)
是将main.m
文件编译成mian.i
文件,指令如下:
clang -E main.m -o main.i
处理源代码中以#
开头的预编译指令。规则如下:
1、#define
删除,并展开对应的宏定义。
2、处理所有的条件预编译指令。如#if
、#ifdef
、#else
、#endif
。
3、#include
& #import
包含的文件递归插入到此处。
4、删除所有的注释 //
、/**/
等。
5、添加行号和文件名标识。如# 1 "main.m"
(编译调试会用到)。
编译(Compilation)
将main.i
文件编译成main.s
文件,指令如下:
clang -S main.i -o main.s
这个过程就是把上面的main.i
文件进行:词法分析
、语法分析
、静态分析
,优化生成相应的汇编代码,最终生成main.s
文件。
名字 | 解释 |
---|---|
词法分析 |
把源代码的字符序列分割成一个个token(关键字、表示符、字面量、特殊符号) ,比如把标识符放到符号表里面。 |
语法分析 |
生成抽象语法树AST ,此时运算符号的优先级确定了;有些符号具有多重含义也确定了,比如:* 是乘号还是对指针取内容;表达式不合法、括号不匹配等等,都会报错。 |
静态分析 |
分析类型声明和匹配问题。比如整型和字符串相加,肯定会报错。 |
中间语法生成 |
CodeGen 根据AST 自上向下逐步翻译成LLVM IR ,并且对在编译期就可以确定的表达式进行优化,比如代码里面的a=1+3 ,可以优化成a=4 。(假如开启了bitcode ) |
目标代码生成与优化 |
根据中间语法生成依赖具体机器的汇编语言;并优化汇编语言。这个过程中,假如有变量且定义在同一个编译单元里,那么就给这个变量分配空间,确定变量的地址。假如变量或者函数不定义在这个编译单元里面,那就等到链接的时候才能确定地址。 |
汇编(Assembly)
将main.s
文件编译成main.o
文件(也就是我们常说的目标文件),指令如下
clang -c main.s -o main.o
这个过程就是把上面得到的main.s
文件里面的汇编指令翻译成机器指令,最终生成等到main.o
链接(Linking)
这个过程就是将main.o
编译成对应的Mach-O
文件,也就是我们常说的可执行文件,指令如下:
clang main.o -o main
我们之前讲过:链接的本质就是把一个或多个目标文件和需要的库(静态库/动态库,如果需要的话)组合成一个文件(
Mach-O
可执行文件)
那么以上就是编译与链接的整个过程。