研读《程序员的自我修养—链接、装载与库》

前言

《编译与链接过程的思考》
《静态库与动态库的思考》
在写完上面两篇思考之后,仔细研读《程序员的自我修养—链接、装载与库》,对编译、链接、装载、静态库和动态库有连贯的认知。

这种知识先在学校学习一遍,然后遗忘;
工作用到,百思不得其解;
然后再看书,才能深深记住和理解。

正文

机器指令

最初的机器指令,是使用纸带来记录;
当变更指令的时候,需要程序员重新计算每个子程序的跳转地址。这个操作就是重定位。
但是,如果有多条纸带,跳转更为复杂。

汇编语言

为了解决上面复杂的机器指令跳转,先驱者发明了汇编语言。
举例:一个汇编指令

 jmp foo

由汇编器在每次汇编程序的时候,重新计算foo这个符号的地址。

符号(Symbol)表示的是一个地址,可能是函数的起始地址,也可能是变量的起始地址。

随着软件的规模越来越大,代码量越来越大;
人们考虑把不同的功能模块以特定的方式组织起来,便于阅读;
那么如何解决,模块最后组合成一个单一的程序的问题?

链接

先来看看模块间的调用有哪些:
1、函数调用;
2、变量访问;
其实可以统一为跨模块的符号引用。
这个统一模块间符号的引用的过程,就是链接。
链接包括:地址和空间分配、符号决议和重定位。

简单描述下链接的过程:
假如主程序main.c 使用了 fun.c 模块的 foo函数,那么main.c在编译的过程,对于调用foo函数的指令,对于指令的目标地址暂时搁置;待到链接的时候,由链接器来填写foo函数的地址。

编译之后会产生目标文件。
目标文件:编译器编译源代码后产生的文件,没有经过链接的过程,某些符号还没有调整过,Windows下的.obj文件,Linux下的.o文件,Unix的.out文件。

静态链接

静态链接:链接器在链接时将静态库合并到可执行程序。
链接器为目标文件分配地址和空间有两层含义:
1、输出的可执行文件的空间;
2、装载后的虚拟地址中的虚拟地址空间;

链接过程分为两步:

  • 1、空间和地址分配,扫描所有的目标文件,获得各个段的长度、属性、位置信息,并把所有的符号定义以及引用收集起来,放到全局的符号表中;
    通过所有段的长度,计算和合并后的长度和位置,并建立映射关系;

  • 2、符号解析和重定位,使用上一步收集到的信息,读取文件中段的数据和重定位信息,进行符号解析和重定位;

.lib、.a是常见的静态链接库;
静态库的缺点:
浪费内存和磁盘空间、更新困难;

动态链接

动态链接:把链接的过程推迟到运行时再进行。
动态链接涉及到运行时的链接以及文件的装载,故而需要操作系统的支持。
程序与.so文件之间的链接是由动态链接库完成的,静态链接是由静态链接器ld完成的。

动态库也需要参与链接的过程,否则找不到该符号的信息;
so保存了完整的符号信息,链接器解析符号时会获取这些信息,用于判断一个符号是否为动态符号;

.dll、.so 是常见的动态链接库;
共享对象的最终装载地址在编译时是不确定的,根据装载时的地址空间的空闲情况,动态分配一块足够大小的虚拟地址空间给响应的共享对象。

动态链接器是动态链接还是静态链接?
静态链接。它要解决其他共享对象的依赖问题,不能依赖其他共享对象;

外部符号:在本目标文件引用但没有定义的符号;(External Symbol)
当多个同名符号冲突的时候,先装入的符号优先,这种优先级方式成为装载序列。(Load Ordering)

iOS相关

我们通过一个工程,来验证动态库的动态装载。

dlfcn.h的方法

  • dlopen打开动态链接库;
  • dlerror返回错误;
  • dlsym获取函数名或者变量名;
  • dlclose关闭动态库;

Objective-C的方法

  • NSClassFromString根据名字返回类;
  • NSSelectorFromString根据名字返回方法;
  • performSelector执行方法;

工程设置
注意,在Linked的设置里面没有ALib,因为我们是通过dlopen的形式来打开动态库。
BLib中有一个OC类, 其中的+load方法,会显示BLib是何时被装载;
ALib中有一个OC类, 其中的+load方法,会显示ALib是何时被装载;还有一个foo函数,为c函数;

测试代码

#include <dlfcn.h>

typedef void (*FOO_FUNC)(void);

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self testLib];
    });
}

- (void)testLib {
    NSLog(@"Test lib.");
    void *handle;
    char *error;
    handle = dlopen("ALib.framework/ALib", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "%s\n", dlerror());
        return ;
    }
    dlerror();
    FOO_FUNC func = dlsym(handle, "foo");
    if ((error = dlerror()) != NULL)  {
        fprintf(stderr, "%s\n", error);
        return;
    }
    func();
    
    
    Class Alib = NSClassFromString(@"ALib");
    SEL foo = NSSelectorFromString(@"foo");
    
    if (Alib && foo) {
        [[[Alib alloc] init] performSelector:foo withObject:nil];
    }
    
    dlclose(handle);
}

测试输出

2016-12-22 13:09:00.653406 testLib[8279:1718634] load Blib
2016-12-22 13:09:04.083711 testLib[8279:1718634] Test lib.
2016-12-22 13:09:04.195297 testLib[8279:1718634] load Alib
foo in ALib.
2016-12-22 13:09:04.195752 testLib[8279:1718634] foo in NSALib.

结果思考
Xcode工程link设置上的动态库,会在程序启动时加载到内存,即使你没有用到这个库的函数;(测试代码中没有用到BLib动态库的代码,但是启动即加载了BLib)

dispatch_after是为了延迟,模拟动态加载的过程;
动态库ALib在调用的时候再进行了装载,并且c函数和Objective-C方法均可调用;(测试输出中,loadAlib比loadBLib晚了3秒钟)

Xcode工程设置的Other Linker Flags

  • -ObjC,告诉链接器把库中定义的Objective-C类和Category都加载进来;(如果静态库中有类和category的话,需要添加这个标志)
  • -all_load,-all_laod会强制链接器把目标文件都加载进来,即使没有objc代码。(库中只有category没有类的时候,即使有-ObjC, 仍然无法加载category)
  • -force_load,必须跟一个静态库的路径,与-all_load不同的是只会完全加载一个库,不影响其他库文件;

总结

在学习现在的知识时,回顾曾经的发展道路,会有意想不到的收获。

航天飞机的宽度是由马屁股决定的。

代码可以在Github找到。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容