【iOS】—— 编译链接
编译流程
编译流程分为四步
- 预处理(Prepressing)
- 编译(Compilation)
- 汇编(Assembly)
- 链接(Linking)
预处理(预编译Prepressing)
作为编译的第一步,首先将main.m
文件编译成了main.i
文件,指令如下:
clang -E main.m -o main.i
预处理的过程虽然是将main.m文件编译成了mian.i文件,但是实际上的过程并不是这么简单的,那么具体的预编译情况是什么样的呢:
预编译是要处理源代码中以#开头的所有预编译指令。规则如下:
-
#define
删除,并展开对应的宏定义。 - 处理所有的条件预编译指令。如
#if
、#ifdef
、#else
、#endif
。 -
#include
&#import
包含的文件递归插入到此处(含#处)。 - 删除所有的注释 //、/**/等。
- 添加行号和文件名标识。如 # 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可执行文件)
动态库和静态库
静态库
之所以称为静态库,是因为在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。
静态库特点:
- 静态库对函数库的链接是放在编译时期完成的。
- 程序在运行时与函数库再无瓜葛,移植方便。
- 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
动态库
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行时才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
动态库特点:
- 动态库把对一些库函数的链接载入推迟到程序运行的时期。
- 可以实现进程之间的资源共享。(因此动态库也称为共享库)将一些程序升级变得简单。
- 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。
静态库和动态库的区别
编译方式不同:
- 静态库是在编译时将库的代码打包到可执行程序中,因此生成的可执行程序包含了所有用到的库函数的代码。这样,当程序被调用时,需要使用哪些库函数就直接从可执行文件中取出来使用。因为代码打包进了可执行程序中,因此静态库的生成通常需要在代码的编译阶段进行。
- 动态库则是在运行时动态加载到程序中的,因此生成的可执行文件并不包含库函数的实现代码,而只是引用了动态库的接口。当程序调用到该库函数时,操作系统会将该函数从动态库文件中加载到内存中供程序运行使用。这样一来,程序的可执行文件会比静态库生成的可执行文件小很多。因为代码加载是在程序运行时进行的,所以动态库的链接通常是在程序运行之前进行。
内存使用方式不同:
- 由于静态库的代码被打包进了可执行程序中,所以在程序运行时,静态库中的代码被复制到了程序使用的内存中,并一直驻留在内存中使用,因此不需要占用额外的内存空间。
- 而动态库的代码在程序运行时才会被加载到内存中,因此动态库的代码实现被复制进内存,会占用额外的内存空间。但是与静态库相比,动态库的内存使用方式具有更好的空间和性能优势,因为多个程序可以共享同一个动态库,而不需要重复加载相同的库文件,从而减少了系统的内存占用。
更新和维护方式不同:
- 静态库的代码被打包成可执行程序的一部分,因此静态库的更新和维护需要重新进行编译和部署,才能让所有使用了该静态库的程序都能够得到更新的代码。
- 动态库可以独立于程序进行更新,因为动态库作为一个单独的文件存在于系统中,可以被多个程序共享。因此,当需要更新动态库时,只需要替换掉旧的动态库文件,不需要重新编译和部署所有使用了该动态库的程序。
因此,如果需要多个程序共享同一个库,或者需要较少的内存占用,则使用动态库可能更为合适。如果需要保持部署和更新的稳定性,则静态库可能更为适合。
dyld
认识 dyld :动态链接器dyld(the dynamic link editor)
是苹果的动态链接器,是苹果操作系统一个重要组成部分,在系统内核 XNU 完成 Mach-O 文件的加载,做好程序准备工作之后,交由 dyld
负责余下的工作。在 macOS 系统中,dyld 位于 D/usr/lib/dyld
。文章来源:https://www.toymoban.com/news/detail-562317.html
dyld2.0和dyld3.0的区别
dyld 3 包含这三个部分:文章来源地址https://www.toymoban.com/news/detail-562317.html
-
进程外 Mach-O 分析器和编译器
(out-of-process mach-o parser)
由于 dyld 2 存在的问题,dyld 3 中将采用提前写入把结果数据缓存成文件的方式构成一个 lauch closure(可以理解为缓存文件) -
进程内引擎 执行 launch closure 处理
(in-process engine)
验证”lauch closures“是否正确,映射dylib,执行main函数。此时,它不再需要分析mach-o header和执行符号查找,节省了不少时间。 -
launch closure 缓存服务
(launch closure cache)
系统程序的 lauch closure 直接内置在 shared cache 中,而对于第三方APP,将在APP安装或更新时生成,这样就能保证 launch closure 总是在 APP 打开之前准备好。
到了这里,关于【iOS】—— 编译链接的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!