前言
就我而言,iOS开发的过程中接触到的编译链接方面的知识很少,这部分知识还是很重要的。
对于iOS的编译链接过程来说并不难,和微机原理的汇编过程还是挺像的。今天对于编译链接的过程学习和了解一下。
参考:iOS程序员的自我修养-编译、链接过程
参考:iOS编译过程
计算机语言
计算机语言有机器语言,汇编语言和高级语言。对于OC这种高级语言来说分为 编译语言和解释型语言。
- 编译型语言(一次性翻译)
-
- 特点:一次性翻译。
-
- 编译型语言的程序只要等编译器编译之后,每次运行都可以直接运行,OC和swift就是如此。
-
- 优点就是执行速度够快,因为不用多次编译。
-
- 缺点就是可移植性差,编译的时候需要对操作系统的库做出链接,可能需要不同的库。
- 解释型语言(逐步翻译)
-
- 解释语言编写的程序在每次运行时都需要通过解释器对程序进行动态解释和执行,如
php,javascript
等
即解释一条代码,执行一条
- 解释语言编写的程序在每次运行时都需要通过解释器对程序进行动态解释和执行,如
-
- 优点就是可移植性好,不需要繁杂的系统库。
-
- 缺点显而易见就是执行速度慢,不是一次性的。
文件后缀名
在iOS的编译和链接过程中,不同的文件后缀名代表不同的文件类型和中间过程。
-
.i
文件:.i
文件是预处理器处理源代码之后生成的文件,其中包含了宏展开、条件编译等预处理操作后的代码。 -
.s
文件:.s
文件是汇编器生成的汇编代码文件。它将预处理器生成的.i
文件翻译成机器指令的文本表示形式,每个汇编指令对应一条机器指令。 -
.o
文件:.o
文件是编译器生成的目标文件,也被称为对象文件。它包含了汇编器生成的机器指令以及一些符号表、重定位信息和其他调试信息。 -
.m
文件:.m
文件是 Objective-C 源代码文件的常见扩展名。它包含了 Objective-C 的代码,可以与 C 和 C++ 代码混合使用。
这些文件在编译和链接过程中的不同阶段产生,并在整个过程中相互转换和传递,最终生成可执行文件或库文件。
编译和链接的过程
上面说过OC/swift都是编译语言,在执行的时候通过编译器生成机器码,机器码可以直接在CPU上执行,效率更快。
OC的编译是基于Clang/ LLVM来编译的,简单了解LLVM是一个模块化的可重用的编译器和工具技术的集合,Clang是LLVM的子程序,C,C++,OC都是Clang的子程序。其目的就是更快的效率编译出更好的程序。
编译链接过程:预处理 -> 词法分析 -> 语法分析 -> 静态分析 -> 生成中间代码和优化 -> 汇编 -> 链接 =
预处理 -> 编译 -> 汇编 -> 链接
编译链接
1,预处理:macro 宏, import 头文件替换及处理其他的预编译指令,产生.i文件
。(都是以#号开头)
2,编译:把预处理完的一系列文件进行一系列词法、语法、语义分析,并且优化后生成相应的汇编代码,产生.s文件
;
3,汇编:汇编器将汇编代码生成机器指令,输出目标文件,产生.o文件
(根据汇编指令和机器指令的对照表一一翻译就可以了);
4,链接:在一个文件中可能会到其他文件,因此,还需要将编译生成的目标文件和系统提供的文件组合到一起,这个过程就是链接。经过链接,最后生成可执行文件
。
经过编译和链接,才会把写的代码转换成计算机能识别的二进制指令。
预处理(预编译)-> 产生.i文件
clang -E main.m -o main.i
处理源代码文件中的以"#"开头的预编译指令
- "#define"删除并展开对应宏定义。
- 处理所有的条件预编译指令。如
#if/#ifdef/#else/#endif。
-
"#include/#import"
包含的文件递归插入到此处。 - 删除所有的注释"//或/**/"。
- 添加行号和文件名标识。
如“# 1 "main.m"”
,编译调试会用到。
总结编译器在预处理阶段处理结果:
- 宏替换 (在源码中使用的宏定义会被替换为对应的内容)
- 头文件引入
(#include,#import)
使用对应文件.h的内容替换这一行的内容,所以尽量减少头文件中的#import,使用@class替代,把#import放到.m文件中。) - 处理条件编译指令
(#if,#else,#endif)
编译 -> 产生.s文件
clang -S main.i -o main.s
编译过程也分为 词法分析 -> 语法分析 -> 静态分析 最后优化生成相应的汇编代码,得到.s文件
- 词法分析 :这一步把源文件中的代码转化为特殊的标记流,源码被分割成一个一个的字符和单词,在行尾Loc中都标记出了源码所在的对应源文件和具体行数,方便在报错时定位问题。
- 语法分析:这一步是把词法分析生成的标记流,解析成一个抽象语法树(abstract syntax tree – AST),同样地,在这里面每一节点也都标记了其在源码中的位置。此时运算符号的优先级确定了;有些符号具有多重含义也确定了,比如“*”是乘号还是对指针取内容;表达式不合法、括号不匹配等,都会报错。
- 静态分析 :分析类型声明和匹配问题,如出现方法被调用但是未定义、定义但是未使用的变量等,以此提高代码质量。
-
- 类型分析:在此阶段clang会做检查,最常见的是检查程序是否发送正确的消息给正确的对象,是否在正确的值上调用了正常函数。如果你给一个单纯的
NSObject*
对象发送了一个hello
消息,那么 clang 就会报错,同样,给属性设置一个与其自身类型不相符的对象,编译器会给出一个可能使用不正确的警告。
- 类型分析:在此阶段clang会做检查,最常见的是检查程序是否发送正确的消息给正确的对象,是否在正确的值上调用了正常函数。如果你给一个单纯的
-
- 其他分析:关于是否多次初始化的检查。
- 中间代码生成和优化: LLVM 会对代码进行编译优化,例如针对全局变量优化、循环优化、尾递归优化等,最后输出汇编代码。
- 目标代码生成和优化: 根据中间语言生成依赖具体机器的汇编语言。并优化汇编语言。
汇编 -> 产生.o文件
clang -c main.s -o main.o
在这一阶段,汇编器将上一步生成的可读的汇编代码转化为机器代码。最终产物就是 以 .o 结尾的目标文件。使用Xcode构建的程序会在DerivedData目录中找到这个文件。
链接
clang main.o -o main
这一阶段是将上个阶段生成的目标文件和引用的静态库链接起来,最终生成可执行文件,链接器解决了目标文件和库之间的链接。
使用clang main.m生成可执行文件a.out(不指定名字默认为a.out),使用file a.out
可以看到其类型信息:
a.out: Mach-O 64-bit executable x86_64
可以看出可执行文件类型为 Mach-O
类型,在 MAC OS 和 iOS 平台的可执行文件都是这种类型。
Mach-O(Apple官方文档)
动态库和静态库
参考: iOS 静态库动态库看这里
链接的过程是将文件和引用的静态库和动态库链接起来,那么什么是动态库和静态库,他们又通过什么来链接的?
1. 静态库
静态库是静态链接库;是多个目标文件经过压缩打包后形成的文件包。以下都是静态库的类型
- Windows 的
.lib
- Linux 的
.a
- MacOS 独有的
.framework
静态库
-
优点::静态库在编译链接时会被整合到可执行文件中,使得程序在运行时独立运行,不需要依赖外部库。
快速,由于整合到可执行文件中,静态库的调用通常比动态库的调用更快,因为不需要动态链接 -
缺点: 生成的可执行程序较大。如果多个使用静态链接生成的程序同时运行会占用大量的内存空间静态库一旦被编译链接到可执行文件中,想要更新或替换静态库的代码需要重新编译整个程序。
如果多个程序都使用相同的静态库,每个程序都会包含一份相同的库代码,造成重复代码
2. 动态库
- 动态库是动态链接库,是实现共享函数库的一种方式。
- 动态库在编译的时候不会被拷贝到目标程序中,目标程序只会存储下动态库的引用。
- 真正用到动态库内的函数时才会去查找 - 绑定 - 使用函数。
- 动态库的格式有:
.framework
、.dylib
、.tbd
……
动态库:
- 优点:节省磁盘空间,且多个用到相同动态库的程序同时运行时,库文件会通过进程地址空间进行共享,内存当中不会存在重复代码,动态库的更新和替换比较方便,只需要替换动态库文件即可,不需要重新编译整个程序。动态库在程序运行时动态链接,使得程序文件较小,更易于分发和部署。
- 缺点: 必须依赖动态库,否则无法运行。动态库在程序启动时需要加载,可能会稍微增加程序的启动时间。兼容性:由于不同操作系统或者不同版本的动态库可能存在兼容性问题,需要特别注意版本兼容性。
3. 动态库和静态库的区别
- 静态库
-
- 在编译时加载
-
- 优点:代码装载和执行速度比动态库快。
-
- 缺点:浪费内存和磁盘空间,模块更新困难。
- 动态库
-
- 在运行时加载
-
- 优点:体积比静态库小很多,更加节省内存。
-
- 缺点:代码装载和执行速度比静态库慢。
- 备注:
-
- 体积小于
最小单位16k
的静态库编译出来的动态库体积会等于16k。
- 体积小于
-
- 换成动态库会导致⼀些速度变低,但是会通过延迟绑定(Lazy Binding)技术优化。
-
- 延迟绑定:首次使用的时候查找并记录方法的内存地址,后续调用就可以省略查找流程。
4. 动态库、静态库、framework是什么关系?
- 库是已经编译完成的二进制文件。
- 代码需要提供给外部使用又不想代码被更改,就可以把代码封装成库,只暴露头文件以供调用。
- 希望提高编译速度,可以把部分代码封装成库,编译时只需要链接。
- 库都是需要链接的,链接库的方式有静态和动态,所以就产生了静态库和动态库。
framework
是一种文件的打包方式,把头文件,二进制文件,资源文件封装在一起,方便管理和分发。所以动态库和静态库的格式都有.framework;
文章来源:https://www.toymoban.com/news/detail-573485.html
总结
编译链接是一iOS文件从开始到变成可执行的文件的过程,我们不去研究原理,但是对于整个流程的掌握还是很有必要的。文章来源地址https://www.toymoban.com/news/detail-573485.html
到了这里,关于【iOS内存管理-编译链接的过程】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!