最近有幸阅读了《高级C/C++编译技术》深受启发,该书深入浅出地讲解了构建过程(编译、链接)中的各种细节,从多个角度展示了程序与库文件或代码的集成方法,提出了面向代码复用和系统集成的软件架构设计方法,以及系统开发过程中疑难问题的解决方案。
  以下将回头记录下其中的关键要点,以便后面查阅。

本节思维导图

1. 关于-fPIC编译器选项

1.1 -fPIC代表什么

  “PIC”是位置无关代码(Position-independent Code)的缩写,说到位置无关代码,我们会立马想到加载重定位,加载重定位将动态库加载到进程内存空间中,但是只有第一次加载这个动态库的进程可以使用它,当其它进程需要加载同一动态库的时候,除了将动态库的完整副本加载到自身内存空间以外,别无他法,当更多的进程需要加载某一特定的动态库时,内存中会存在更多的副本。这种限制的根本原因在于加载过程设计的缺陷,在将动态库加载到进程中之前,装载器需要修改动态库的代码段,使得在加载该库的进程中,动态库的所有符号是有意义的,即便这种方法可以满足基本的运行需求,但其导致的最终结果是由于动态库代码的修改是不可逆的,因此其它进程难以直接复用这个已加载的动态库。

  为了解决加载重定位的缺陷,重新设计加载机制,避免将加载的动态库代码段绑定到第一个加载该动态库的进程中,提出了PIC机制,使得多个进程可以无缝映射到已加载动态库的内存映射中

1.2 一定要使用-fPIC编译器选项来创建吗?

  在32位体系架构中,我们不需要使用-fPIC编译器选项,如果没有指定该选项,编译出来的动态库就会遵循旧式的装载时重定位机制

  在64位体系结构中,简单地忽略-fPIC编译器选项就会导致链接错误,要修正链接错误,一种方法是向编译器传递-fPIC选项,另一种方法是向编译器传递-mcmodel=large选项

1.3 只有在编译动态库时才会使用-fPIC编译选项吗?能否在静态链接库的情况下使用?

  在32位体系结构中,编译静态库时是否使用-fPIC选项是无所谓的,这样会对编译生成的代码结构产生一定的影响,但是对于静态库的链接和运行时行为的影响是微乎其微的

  在64为体系结构中,情况会变得更加有意思:

  (1)如果静态库是链接到可执行文件中,那么编译时可以指定也可以不指定,

  (2)如果静态库链接到的是动态库,那么必须使用-fPIC选项编译

2. C++引起的链接问题

2.1 C++使用了更加复杂的符号命名规则

  为了唯一地标识函数,连接器在为函数入口点建立符号的时候,必须使用某种方法来包含函数的从属信息和输入参数信息,链接器的设计为了满足这种更加复杂的需求,最终产生了“名称修饰”这种技术。名称修饰是将函数名、函数的从属信息、函数的参数列表进行组合,最后生成符号名称的过程。

  为了统一,当我们希望避免名称修饰时,必须使用一个特殊的关键字来告知连接器不要修饰符号名称

  1. #ifdef __cplusplus
  2. extern "C"
  3. {
  4. #endif // __cplusplus
  5. void fun1(void);
  6. void fun2(void);
  7. void fun3(void);
  8. void printMessage(void);
  9.  
  10. #ifdef __cplusplus
  11. }
  12. #endif // __cplusplus

2.2 静态初始化顺序问题

  C语言中的一项遗留特性:链接器可以处理很简单的初始化变量,无论是简单数据类型还是结构体,连接器只需要在数据段中保留存储空间,并将初始值写入该位置即可,在C语言领域,变量初始化的顺序通常不是很重要,关键在于变量的初始化其实在程序启动时候就完成了。

  但是在C++中,数据类型往往是一个对象,对象的初始化是在运行时通过对象构造函数来完成的,为了初始化C++对象,连接器需要做更多的工作,为了帮助连接器完成其任务,编译器将特定文件需要使用的所有构造器的列表嵌入到目标文件中,并将相关信息存放在特定的目标文件段中,在连接时,连接器会检查所有的目标文件,并将其中的构造函数列表合并成完整的列表,以备运行时执行,说了这么多,总的一句话是C++对象的初始化,加重了编译器和连接器的负担,由于连接器依然不够智能,在大多数情况下,程序在加载时会引起非常严重的崩溃,而且是在任何调试器能够捕捉到之前

  发生这种情况是因为初始化的对象依赖于另外一些需要在器之前初始化的对象,并且没有任何规则可以指定静态对象的初始化顺序,我们将这类问题通常称为静态初始化顺序问题。

解决方案:

(1)为_init()函数提供自定义实现,这是一个在动态库加载时会被立即调用的标准函数,在该函数中可以通过静态成员函数初始化对象,以通过构造函数强制初始化,因此,也可以为_fini()函数提供自定义实现

(2)调用一个自定义函数去访问特定对象,而不是直接访问该函数会包含C++类的一个静态实例,并返回其引用

2.3 模版

这涉及到编译器的设计问题:

(1)编译器可以保证生成所有的模版特殊化代码,并为每个特殊化版本创建一个弱符号

(2)连接器在链接结束之前都不包含模版特殊化的机器码是想爱你,但其余所有的链接任务都完成后,连接器会检查代码,确定到底需要哪些特殊化版本,并调用C++编译器创建所需的模版特殊化,最后,将机器码插入可执行文件中

3. 控制动态库符号的可见性

  在Linux中所有动态库连接器符号默认都是外部可见的,任何尝试链接这些动态库的用户都可以访问这些符号

  在windows中,DLL链接符号默认都是外部不可见的

3.1 导出linux动态库符号

(1)方法一

  通过向编译器传递编译选项-fvisibility=hidden就可以将所有的动态库符号置为对外不可见,默认可见

(2)方法二

  __attibute__((visibility("<default | hidden>")))

  通过在函数前面使用编译器属性修饰,可以指示链接器允许或禁止对外部提供该符号

(3)方法三

  #pragma visibility push(hidden)

  #pragma visibility pop

3.2 导出windows动态链接库符号

  __descspec(dllexport)

4. 动态库链接模式

  (1)加载时动态链接

  (2)运行时动态链接

目的 Linux版本 Windows版本
加载库 dlopen() LoadLibrary()
查找符号 dlsym() GetProcAddress()
卸载库 dlclose() FreeLibrary()
错误报告 dlerror() GetLastError()

示例伪代码:

  1. handle = do_load_library("<library path>", optional_flags);
  2. if(NULL == handle)
  3. {
  4. report_error();
  5. }
  6.  
  7. pRunction = (function_type)do_find_library_symbol(handle);
  8. if(NULL == pFunction)
  9. {
  10. report_error();
  11. unload_libray();
  12. handle = NULL;
  13. return;
  14. }
  15.  
  16. pFunction(function arguments);
  17.  
  18. do_unload_library(handle);
  19. handle = NULL;

高级C/C++编译技术之读书笔记(三)之动态库设计的更多相关文章

  1. 高级C/C++编译技术之读书笔记(一)之编译/链接

    最近有幸阅读了<高级C/C++编译技术>深受启发,该书深入浅出地讲解了构建过程(编译.链接)中的各种细节,从多个角度展示了程序与库文件或代码的集成方法,提出了面向代码复用和系统集成的软件架 ...

  2. 高级C/C++编译技术之读书笔记(二)之库的概念

    最近有幸阅读了<高级C/C++编译技术>深受启发,该书深入浅出地讲解了构建过程(编译.链接)中的各种细节,从多个角度展示了程序与库文件或代码的集成方法,提出了面向代码复用和系统集成的软件架 ...

  3. 高级C/C++编译技术之读书笔记(四)之定位库文件

    最近有幸阅读了<高级C/C++编译技术>深受启发,该书深入浅出地讲解了构建过程(编译.链接)中的各种细节,从多个角度展示了程序与库文件或代码的集成方法,提出了面向代码复用和系统集成的软件架 ...

  4. 高级C/C++编译技术之读书笔记(五)之动态库版本控制

    最近有幸阅读了<高级C/C++编译技术>深受启发,该书深入浅出地讲解了构建过程(编译.链接)中的各种细节,从多个角度展示了程序与库文件或代码的集成方法,提出了面向代码复用和系统集成的软件架 ...

  5. Struts2技术内幕 读书笔记三 表示层的困惑

    表示层能有什么疑惑?很简单,我们暂时忘记所有的框架,就写一个注册的servlet来看看. index.jsp <form id="form1" name="form ...

  6. 深入理解linux网络技术内幕读书笔记(三)--用户空间与内核的接口

    Table of Contents 1 概论 1.1 procfs (/proc 文件系统) 1.1.1 编程接口 1.2 sysctl (/proc/sys目录) 1.2.1 编程接口 1.3 sy ...

  7. 深入探索Android热修复技术原理读书笔记 —— 代码热修复技术

    在前一篇文章 深入探索Android热修复技术原理读书笔记 -- 热修复技术介绍中,对热修复技术进行了介绍,下面将详细介绍其中的代码修复技术. 1 底层热替换原理 在各种 Android 热修复方案中 ...

  8. 深入探索Android热修复技术原理读书笔记 —— 资源热修复技术

    该系列文章: 深入探索Android热修复技术原理读书笔记 -- 热修复技术介绍 深入探索Android热修复技术原理读书笔记 -- 代码热修复技术 1 普遍的实现方式 Android资源的热修复,就 ...

  9. 深入探索Android热修复技术原理读书笔记 —— so库热修复技术

    热修复系列文章: 深入探索Android热修复技术原理读书笔记 -- 热修复技术介绍 深入探索Android热修复技术原理读书笔记 -- 代码热修复技术 深入探索Android热修复技术原理读书笔记 ...

随机推荐

  1. Jconsle

    1. jconsole 远程连接: JConsole很好用,可以解决很多疑难杂症.但远程连接需要设置一下Java opt才可以使用.以下是步骤: 1). 在java opt下添加如下内容: 如果是无须 ...

  2. [转]从程序员到CTO的Java技术路线图

    原文链接:http://zz563143188.iteye.com/blog/1877266 在技术方面无论我们怎么学习,总感觉需要提升自已不知道自己处于什么水平了.但如果有清晰的指示图供参考还是非常 ...

  3. NEVER QUIT. NEVER SAY NEVER.

    有志者不是从不失败,而是从不妥协. NEVER QUIT. NEVER SAY NEVER. 2015/09/15 Winners are not those who never fail but t ...

  4. ARM协处理器CP15寄存器详解【转】

    本文转载i自;https://blog.csdn.net/gameit/article/details/13169405 用于系统存储管理的协处理器CP15   MCR{cond}     copro ...

  5. windows技巧--优雅的设置环境变量,其实只是为了节约几秒宝贵的时间

    优雅的设置windows环境变量 环境变量的作用 将应用程序设置在环境变量以后,可以直接在cmd里面或者运行窗口中执行程序 分类 系统环境变量 对系统中所有用户有效,修改过后要重启生效 用户环境变量 ...

  6. idea调节字体大小

    这是调节前的 这是调节后的 步骤

  7. PAT1063. Set Similarity (25)

    来自http://blog.csdn.net/tiantangrenjian/article/details/16868399 set_intersection 交集  set_union 并集  s ...

  8. AtCoder ARC097C Sorted and Sorted:dp

    传送门 题意 有 $ 2n $ 个球排成一行,其中恰好有 $ n $ 个白球和 $ n $ 个黑球.每个球上写着数字,其中白球上的数字的并集为 $ \lbrace 1 \dots n\rbrace $ ...

  9. selenium学习笔记(简单的元素定位)

    收拾一下心情开始新的一周工作 继续是selenium的学习.配置成功后 由于所有操作都是建立在页面元素基础上的.所以下来就是学习定位元素 首先是基础的定位.就使用博客园首页搜索框为例: 下面是代码: ...

  10. SPOJ - LCS2

    后缀自动机板子题 https://vjudge.net/problem/28017/origin 找多串的最长公共子串 //#pragma comment(linker, "/stack:2 ...