一个iOS程序员的自我修养(一)编译和链接

3,115 阅读4分钟

被隐藏了的过程

对于平常的应用开发,我们很少关注编译和链接过程,因为 Xcode 在 build 的时候将编译和链接合并到了一起一步完成。以mian.m为例:

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hellow World!");
    }
    return 0;
}

它的编译和链接过程如下:

以上过程可以被成 4 个步骤,分别是预处理,编译,汇编和链接。

Preprocessor(预处理)

预处理主要处理那些源代码中以“#”开头的预编译指令:

clang -E main.m -F /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks 

主要处理规则如下:

  • 将所有的“#”删除,并且展开所有的宏定义。
  • 处理所有条件预编译指令,比如“#if”、“#ifdef”、“#elif”、“#else”、“#endif”。
  • 处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。
  • 删除所有的注释。
  • 添加行号和文件名标识,比如 #2 “main.m” 2,调试和编译错误警告时能够现实行号。

Compilation(编译)

clang -fmodules -E -Xclang -dump-tokens main.m

编译过程就是将预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。

词法分析

clang -fmodules -E -Xclang -dump-tokens main.m

将源代码的字符序列分割成一系列的符号(Token)。一般可以分为一下几类:关键字、标识符、字面量(数字、字符串)和特殊符号(如加号、等号)。

语法分析

clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

生成以表达式为节点的抽象语法树AST,运算符号的优先级和含义被确定,如果出现表达式不合法,比如各种括号不匹配、表达式中缺少操作符,编译器就会报错。

静态语义分析

通常包括声明和类型的匹配,类型的转换,比如一个浮点型到整形的转换。将浮点型赋值给指针类型不匹配编译器会报错。

中间语言生成

生成和目标机器与运行时环境无关代码。中间代码使得编译器可以被分为前端和后端,xcode 的编译器前端为 Clang,后端为 LLVM,其中前端负责产生机器无关的中间代码,后端将中间代码转换成目标机器代码。

目标代码生成与优化

这个过程属于编译器后端,主要包括代码生成器和目标代码优化器。代码生成器将中间代码转换成目标机器的汇编代码,代码优化器对目标代码进行优化,选择合适的寻址方式,使用位移来代替乘法运算、删除多余的指令等。这个阶段如果变量跟源代码定义在了同一个编译单元里面,那么编译器可以为它们分配空间和地址,定义在其他模块的全局变量和函数在最终运行时的绝对地址都要在最终链接的时候确定。

Assembly(汇编)

clang -c main.s -o main.o

汇编是将汇编代码转换成机器可以执行的指令,输出目标文件.o。

Linking(链接)

编译器将源代码文件编译成了未链接的目标文件,链接器将这些目标文件链接成可执行文件,可以是静态库或者动态库。这个过程主要包括地址和空间分配、符号决议和重定位。比如说有个全局变量 var 定义在目标文件 A 里面,目标问文件 B 里面要访问这个全局变量,由于在编译目标文件 B 的时候,编译器并不知道 var 变量的目标地址,所以将目标地址设置为 0,等待链接器将目标文件 A 和目标文件 B 链接起来的时候再将其修正,这个地址修正的过程也叫做重定位。

引用

《程序员的自我修养》