.m --> .i前端
主要处理那些源代码文件中的以“#”开始的预编译指令。好比 “#include”、“#define ”等
复制代码
1.将全部的 “#define ”删除,而且展开全部的宏定义。程序员
2.处理全部条件预编译指令,好比 “#if”、“#ifdef”、“#elif”、“#else”、“#endif ”。”swift
3.处理 “#include ”预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其余文件。后端
4.删除全部的注释“//”和“/* */”。markdown
5.添加行号和文件名标识,好比#2“hello.c”2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时可以显示行号。函数
6.保留全部的 #pragma 编译器指令,由于编译器需要使用它们。post
.i --> .s优化
Objective C/C/C++
使用的编译器前端是Clang,Swift是swift,后端都是LLVM.spa
编译过程通常能够分为6步:词法分析(扫描)、语法分析、语义分析、源代码优化、代码生成和目标代码优化
复制代码
这样对于一些能够跨平台的编译器而言,它们能够针对不一样的平台使用同一个前端和针对不一样机器平台的数个后端。
复制代码
负责生成机器无关的中间代码
复制代码
词法扫描器 将代码分割生成记号token,好比关键字、标识符、字面量,特殊符号(+、-运算符)
复制代码
使用上下文无关语法,生成语法树,以表达式为节点的树,处理表达式中括号不匹配,缺乏操做符,可是并不了解整个语句的意义翻译
编译期能作的就是静态语义分析,运行时作的是动态语义分析,静态语义分析主要作的是声明与类型的匹配 ,类型的转换
好比一些简单运算 加减表达式能够直接计算肯定 也叫作 三地址码 x = y op z
负责将中间代码转换成目标机器代码
复制代码
这个阶段若是变量跟源代码定义在了同一个编译单元里面,那么编译器能够为它们分配空间和地址,定义在其余模块的全局变量和函数在最终运行时的绝对地址都要在最终连接的时候肯定。
clang -c main.s -o main.o
复制代码
汇编器是将汇编代码转变成机器能够执行的指令,每个汇编语句几乎都对应一条机器指令。因此汇编器的汇编过程相对于编译器来说比较简单,它没有复杂的语法,也没有语义,也不须要作指令优化,只是根据汇编指令和机器指令的对照表一一翻译就能够了”
clang -c main.o -o main.out
复制代码
编译器将源代码文件编译成了未连接的目标文件,连接器将这些目标文件连接成可执行文件,能够是静态库或者动态库。这个过程主要包括地址和空间分配、符号决议和重定位。好比说有个全局变量 var 定义在目标文件 A 里面,目标问文件 B 里面要访问这个全局变量,因为在编译目标文件 B 的时候,编译器并不知道 var 变量的目标地址,因此将目标地址设置为 0,等待连接器将目标文件 A 和目标文件 B 连接起来的时候再将其修正,这个地址修正的过程也叫作重定位
节选自《程序员的自我修养:连接、装载与库》 -- 俞甲子 石凡 潘爱民