静态库与动态库的一些面试问题
关于动/静态库一些问题的随笔,写的比较乱,啊哈哈哈哈哈
常用的文件格式
.a .dylib .framework .xcframework
1. 动态库和静态库的区别是什么?
nm -pa path
动态库所有符号信息都放到一起,
静态库按照.o文件进行分开存储
动态库的代码都是统一放在 Mach-O text section 段的
静态库的代码也是根据.o文件分开存储的
所以说静态库是一个.o文件的合集,静态库在往后链接就是可执行文件,也可以说是(动态库)。
静态库:.o -> .o合集 -> .a(静态库) 链接 -> 可执行文件(动态库)
在终端执行man ld
(ld
是链接器。)命令可以看到下面这段话:
静态库: 是.o文件的合集,在每个.o里面都包含了全局符号,
ld
在需要解析一些符合的时候,才会从将这些符号从.o中链接到主App中。动态库:是一个最终链接完成的镜像,也就是编译链接的最终产物。所以动态库不可以做合并,所以在arm64和armv7同时支持的动态库也不是合并的,只是放在一起,Mach-O根据不同的环境针对性的链接。
所以就编译后的体积来说,同样的代码编译成动态库要小于静态库的体积。但是有时候从文件上来看并不是这样,这是因为静态库里面的.o文件并没有达到对齐所在的空间,所以存在比动态库小的可能。一般就是小于16k的时候,代码不多 。
查看命令 objdump --macho -x 文件路径
查看他的Mach-O段。
总结一下答案:
静态库也就是静态链接库,可以简单的看成目标文件的集合。即很多目标文件经过压缩打包后形成的文件。Windows下的.lib,Linux和Mac下的.a。Mac独有的.framework。
缺点: 浪费内存和磁盘空间,模块更新困难。
动态库:与静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会被真正加载进来。格式有.framework .dylib .tbd。现在苹果允许上架的动态库类型是.framework,有签名,阻止了热更新。
缺点:会导致一些性能损失。但是可以优化,比如延迟绑定(lazy Binding)技术。
如果您对Mach-O
感兴趣,可以看看我的这篇文章Mach-O探索
2.静态库链接到主程序,静态库存放在什么位置?动态库呢?
静态库会在编译连接后与APP主程序合并成一个文件,也就是在我们的APP可执行文件中,动态库由于不能再合并了则需要单独签名放在frameworks文件夹中,在APP主程序文件中,保存着动态库的引用路径,等到APP运行的时候在进行加载。
3. 静态库、动态库与framework的关系?
静态库就是一些.o文件的合集,也就是静态链接库,一般是.a文件或者.framework文件,一般在APP包中看不到静态库,都会被合并的主程序可执行文件中。
动态库一般是.framework .dylib .tbd文件形式。动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会被真正加载进来
framework 是Mac OS/iOS 平台上的一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。
Framework和系统的 UIKit.Framework还是有很大区别的。系统的Framework不需要拷贝到目标程序中,我们自己做出来的Framework哪怕是动态的,最后也还是要拷贝到APP中(APP和Extension的Bundle是共享的),因此苹果又把Framework称为Embedded Framework。
Embedded Framework 也就是嵌入式Framework,开发中使用的动态库放入到ipa下的frameworks目录下,基于沙盒运行。不同的APP使用相同的动态库,并不会只在系统中存在一份。而是会在多个APP包中各自打包、签名、加载一份。
4. 什么是xcframework,使用有什么优势?
Xcode 会自动根据架构来将对应的架构拷贝到ipa中。Xcode 11以上支持。
XCFramework:是苹果官方推荐的、支持的,可以更方便的表示一个多个平台和架构的分发二进制库的格式。
需要Xcode11以上支持。
是为更好的支持Mac Catalyst和ARM芯片的macOS。
专门在2019年提出的framework的另一种先进格式。
目前支持:
iOS/iPad:arm64
iOS/iPad Simulator:x86_64 arm64
Mac Catalyst:x86_64 arm64
Mac:x86_64 arm64
和传统的framework相比:
- 可以用单个.xcframework文件提供多个平台的分发二进制文件;
- 与Fat Header相比,可以按照平台划分,可以包含相同架构的不同平台的文件;
- 在使用时,不需要再通过脚本去剥离不需要的架构体系。
关于Fat Header就是胖二进制文件(Mach-O)的头,也就是Mach-O Universal Files
,通过header
来记录不同架构在文件中的偏移量,segment
占多个分页。所以对于多个架构这个header
就会变得很大,也就是为什么要被叫做Fat Header
5. 什么是dead strip与-Objc参数与-force_load之间有联系吗?
dead strip
与-Objc和-force_load之间没有什么联系。以下四个是对于加载静态库的四个级别
- -noall_load:默认级别,也就是我们的APP默认不加载(链接)静态库中的内容,什么都没用到就不加载
- -all_load:把所有代码全部加载进来,此时就可以看到静态库中的所有符号
- -ObjC:只加载与OC相关的代码
- -force_load
:指定的静态库才会被加载进来,可以通过这个来控制静态库的符号冲突,哪个作为主链接,哪个由ld扫描,不需要加载的就不加载了。当然解决符号冲突还可以通过加前缀的方式。
以上四个只针对我们的链接器去链接我们的静态库的时候使用。
那么dead strip
是干什么的呢?我们通过man ld
命令在终端里找到如下:
所有的.o文件,包括动态库都会生效。删除没有被入口函数(默认是main)访问的函数,或者导出这些符号。导出符号也就是其他文件的导入(#import)。我自己没用,但是导出后别人用了,就是这个意思。
也就是你定义了一个函数,但是并没有被入口函数使用,所以就在编译链接的时候干掉这些方法。再就是没有成为导出符号,也就是对于其他文件通过#import。这是对每个文件都生效的。
如果一个动态库也没有被主函数使用,也没有被导出符号。
意思一个动态库没有被主函数使用,也没有被导出符号,则移除这个动态库。链接信息剥离掉,默认不剥离。
为什么要加载.o,为什么要加载这个符号。
-noall_load 和 -all_load -> 链接器ld
加载静态库 -> App 没有用到静态库的代码,以类为最小单位,没有用到就不加载,如果是分类有时候是没办法判断用没用到,所以一下三方库建议我们使用-Objc
,但是最好使用-force_load
,可以有效减少包体积。
6. 什么是tbd文件,在日常开发中有哪些应用场景?
tbd全称是text-based stub libraries
,本质上就是一个YAML描述的文本文件。
他的作用是用于记录动态库的一些信息,包括导出的符号、动态库的架构信息、动态库的依赖信息
用于避免在真机开发过程中直接使用传统的dylib
对于真机来说,由于动态库都是在设备上,在Xcode上使用基于tbd格式的伪framework可以大大减少Xcode的大小。
tbd
格式文件,本身是通过Xcode内置工具tapi-installapi
专门来生成的,具体路径为:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefaul t.xctoolchain/usr/bin/tapi installapi
7. 要减小APP的体积,应该使用静态库还是动态库,为什么?
一般使用静态库会减小APP体积。
静态库是.o文件的合集,在每个.o里面都包含了全局符号,.o多了就包含了多个全局符号,所以体积就大了。
动态库是最终的编译链接产物,所有符号都放在一起,减少了全局符号存储的次数,所以体积就小。
那么为什么还要使用静态库呢?因为静态库还存在一层编译连接的的过程,会将静态库合并到APP主程序文件中,可以优化,而动态库已经是编译链接的最终产物,还需要拷贝到frameworks文件夹中,所以会增加APP的体积。
其他
模仿生成APP的过程
.m -> .o .m -> .a 静态库 -> 链接去生成APP
静态库链接生成APP倒地存储在什么地方?
静态库的代码与主程序是放在一起的。
APP链接
- 找到头文件,api
- 找到库所在的位置
- 找到库的名称
1. 怎么判断什么场景使用动/静态库?
静态库:ipa体积小,不影响启动速度,但是静态库不能完全剥离符号,只允许剥离调试符号,静态库暴露出来的信息要多一些
动态库:资源文件更好处理,更加灵活,如果你不想暴露更多信息可以使用动态库,比如你是做SDK开发的。
2. 动态库超过6个?加载变慢怎么处理
1. 改成静态库
2. 动态库加载: execve Mach-O -> dyld -> load
3. 静态库符号冲突?
1. 使用-force_load
指定两个冲突的静态库那个完全加载,那个由ld判断要加载的代码
2. 符号冲突,不叫同名符号就可以
1. 有源码,直接修改
2. 没有源码,objcopy->重命名一个符号
3. brew install llvm
4. --redefine-sym=old=new
4. all_load等只针对静态库
all_load只针对静态库,不针对动态库。
5. A静态库引用的B静态库,在项目中报找不到B中的符号?
A中只保存了B静态库的头文件,没有保存符号真正的实现代码,或者说没有保存符号真正代表的代码。
解决办法就是:
- 合并两个静态库
- 把B静态库也加载到APP中
6. 检测没有用到的代码(方法)?
个人觉得可以使用clang插桩,将用到的符号都检测出来,比如二进制重排的时候的order
文件,然后对比所有符号。
或者参考上面这些dead strip
等实现原理,做一些研究。
7. embed
只要是framework都可以embed,无关乎动/静态库
framework只是一种打包格式,里面可以包含一些资源文件。
8. hmap
.o -> 头文件目录/子目录 -> 特定的头文件 -> 花费一定的时间
hmap -> {特定的头文件:特定的头文件地址} -> 编译速度提升
特定的二进制结构
9. Two-level namespace
两级命名空间,默认的动态库是由两级命名空间的。
静态库为什么会符号冲突,就是静态库默认是取符号表找符号,所以两个同名符号就是会冲突。
Two-level namespace 动态库 -> 动态库 -> 符号,所以动态库的符号不会冲突。
source