iOS 编译之链接器:符号与地址的绑定
status
category
date
summary
slug
icon
tags
password
编译是写完程序之后做的第一件事,但自己从来没有了解过相关知识,借着本学期开设编译原理课程的机会,针对开发中常见的问题进行学习和记录。在很久之前的 Mach-O文件结构 一文中,第一次接触到链接器、符号表等概念,就以此展开慢慢学习吧。
解释器、编译器和链接器
解释器(Interpreter)不会一次性翻译所有代码,而是逐行翻译并运行,消除了编译整个程序的负担,大大提高了调试效率,但运行时效率很低。常见的用到解释执行的语言有:JavaScript、Python、Ruby、Shell 等。
编译器(Compiler)一次性地把源代码转换成目标语言,然后打包为可执行文件,运行时无需依赖编译器和其他程序,因此运行速度很快。Android 和 iOS非常追求程序的启动速度,所以都依赖于编译器的一次性编译和打包,对应的,牺牲的就是调试效率,我们修改一处代码,需要全部编译并打包才能进行调试。一般的 C、C++、Objective-C 等程序也都是依赖于编译的。
现在 Apple 使用的编译器是 LLVM(Low Level Virtual Machine),它是一系列编译器工具的集合,其中包含了 Clang,这是一款由 Apple 主导的,用于取代 GCC 的一款编译器;还有 lld,它是 LLVM 的一个子项目,是一个内置的链接器(Linker),链接器的作用是组织可执行文件的地址空间,即将多个编译产生的目标文件和库链接成一个可执行文件。在 iOS 中,编译器对每个文件进行编译,并生成 Mach-O(可执行文件),链接器将项目中多个 Mach-O 文件合并成一个。
编译的过程
链接器做了什么
Example
我们通过编写一个简单的 OC 程序来理解链接的过程。
Boy.h:
Boy.m:
main.m:
xcrun
是Xcode的基本命令行工具,可以通过它调用其他工具,通过以下命令编译上面的文件:编译过后,目录下会出现
Boy.o
和main.o
,这就是目标文件,是可执行文件,链接器的工作就是把这些文件组合成一个可执行文件,通过以下指令来完成这个操作:目录下会出现a.out文件,这是编译器默认的名字。
我们通过nm工具分别查看几个文件的符号,首先看看main.o文件:
_OBJC_CLASS_$_Boy
:表示Boy的OC符号
(undefined) external
:未实现非私有,如果是私有的话,就是 non-external
_main
:main函数,处理0地址,储存在__TEXT,__text区域中
再来看看Boy.o文件:
undefined
表示未定义,在目标文件和动态库做链接处理时,链接器会尝试解析所有的未定义符号。在这个过程中,链接器会记录动态库解析的路径,可以通过查看a.out文件来对比一下区别:对于那些
undefined
的符号,a.out文件中有了更多的信息,可以知道在哪个动态库能够找到它。通过otool工具可以查看动态库的路径:可以看到这些
undefined
的符号需要的两个库是libSystem
和libobjc
。其中dylib
表示动态链接,编译时不会被编译到可执行文件中,只有运行时才链接,这样可以减轻包的负担,还可以在不更新程序的情况下更新动态库。我们可以通过下面的命令打印看看哪些库被加载了:
另外,Foundation 会依赖很多其他的库,这些库的相互依赖会导致符号很多,处理时间很长。系统采用共享缓存来解决这个问题,每个进程都会在自己的地址空间对共享缓存进行映射,当加载 Mach-O 文件时,动态链接器会先检查是否有缓存,这样做可以提高 App 的启动速度。
简单来说,dyld 的工作是:
- 执行 Mach-O 文件,根据文件中 undefined 的符号加载动态库,系统会通过共享缓存来解决链接的递归递归问题
- 将 undefined 的符号绑定到地址上
Loading...