C++编译链接精要

  • C++语言的三大约束: 与C兼容, 零开销(zero overhead)原则, 值语义;

    • 兼容C语言的编译模型与运行模型, 也就是锁能直接使用C语言的头文件和库;
  • 头文件包含具有传递性, 引入不必要的依赖;
  • 头文件是在编译时使用, 动态库文件是在运行时使用, 二者的时间差可能带来不匹配, 导致二进制兼容性方面的问题;

C++的编译模型

  • C++ 继承了单遍编译;

    • 编译器只能根据目前看到的代码做出决策, 读到后面的代码也不会影响前面做出的决定;
    • 这特别影响名字查找(name lookup)和函数重载决议;
  • 使用前向声明来减少编译期依赖;

C++链接(linking)

  • C++与静态链接;
  • C++与动态链接;
  • 传统的是one-pass链接器 -- 越基础的库越是放到后面;
  • C++在C的链接模型上主要增加了两项内容:
    • vague lingkage, 即同一个符号有多份不冲突的定义;
  • 现在的编译器聪明到可以自动判断一个函数是否适合inline, 因此inline关键字在源文件中往往不是必需的;
  • 现在的C++编译器采用重复代码消除的办法来避免重复定义(multiple definition), 其余的则丢弃(vague linkage);
  • 编译器如何处理inline函数中的static变量?

模板

  • C++模板包括函数模板和类模板:

    • 函数定义, 包括具现化后的函数模板、类模板的成员函数、类模板的静态成员函数;
    • 变量定义, 包括函数模板的静态数据变量、类模板的静态数据成员、类模板的全局对象;
  • 模板编译链接的不同之处在于, 以上具有external linkage的对象通常会在多个编译单元被定义;
    • 链接器必须进行重复代码消除, 才能正确生成可执行文件;
  • 模板的定义要放到头文件中, 否则会有编译错误(链接错误);

虚函数

  • 在现在的C++实现中, 虚函数的动态调用(动态绑定, 运行期决议)是通过虚函数表(vtable)进行的, 每个多态class都应该有一份vtable;
  • 定义或继承了虚函数的对象中会有一个隐含成员: 指向vtable的指针, 即vptr;
  • 在构造和析构对象的时候, 编译器生成的代码会修改这个vptr成员, 这就要用到vtable的定义(使用其地址);
  • 我们有时看到的链接错误不是抱怨找不到某个虚函数的定义, 而是找不到虚函数表的定义;
  • 一个多态class的vtable应该恰好被某一个目标文件定义, 这样链接就不会有错;
    • 但是C++编译器有时无法判断是否应该在当前编译单元生成vtable定义, 为了保险起见, 只能每个编译单元都生成vtable, 交给链接器去消除重复数据;
    • 有时我们不希望vtable导致目标文件膨胀, 可以在头文件的calss定义中声明out-line虚函数;

工程项目中头文件的使用规则

  • C++还无法摆脱头文件和预处理, 因此我们要深入理解可能存在的陷阱;
  • 一旦为了使用某个struct或者某个库函数而包含了一个头文件, 那么这个头文件中定义的其他名字(struct, 函数, 宏)也被引入当前编译单元, 可能制造麻烦;
  • 头文件的害处:
    • 传递性, 头文件可以再包含其他头文件;

      • 合理地组织源代码, 减少开发时rebuild的成本是每个稍具规模项目的必做功课;
    • 顺序性, 一个源文件可以包含多个头文件, 但可能会造成程序的语义跟头文件包含的顺序有关, 也跟是否包含某一个头文件有关;
    • 差异性, 内容差异造成不同源文件看到的头文件不一致, 时间差异造成头文件与库文件内容不一致;
  • 现代的编程语言, 模块化做得比较好:
    • 对于解释型语言, import的时候直接把对应模块的源文件解析(parse)一遍(不再是简单地把源文件包含进来);
    • 对于编译型语言, 编译出来的目标文件(例如Java的.class文件)里直接包含了足够的元数据, import的时候只需要读目标文件的内容, 不需要读源文件;
    • 这两种做法都避免了声明与定义不一致的问题, 因为在这些语言里声明与定义是一体的;
    • 同时这种import手法也不会引入不想要的名字, 大大简化了名字查找的负担(无论是人脑还是编译器);
    • 也不用担心import的顺序不同造成代码功能变化;
  • 头文件的使用规则:
    • 几乎每个C++编程都会涉及到头文件的组织;
    • 将头文件的编译依赖降至最小;
    • 将定义式之间的依赖关系降至最小, 避免循环依赖;
    • 让class名字、头文件名字、源文件名字直接相关 -- 这样方便源代码的定位;
    • 令头文件自给自足;
    • 总是在头文件内写内部#include guard(护套), 不要在源文件写外部护套;
    • include guard用的宏的名字应该包含文件的路径全名(从版本管理器的角度), 必要的话还要加上项目名称;

    • 如果编写程序库, 那么公开的头文件应该表达模块的接口, 必要的时候可以把实现细节放到内部头文件中;

工程项目中库文件的组织原则

  • Linux的共享库(shared library)比Windows的动态链接库在C++编程方面要好用得多, 对应用程序来说基本可算是透明的, 跟使用静态库无区别;

    • 一致的内存管理, Linux动态库与应用程序共享同一个heap, 因此动态库分配的内存可以交给应用程序去释放, 反之亦可;
    • 一致的初始化, 动态库里的静态对象(全局对象、namespace级的对象等等)的初始化和程序其他地方的静态对象一样, 不用特别区分对象的位置;
    • 在动态库的接口中可以放心地使用class、STL、boost(如果版本相同);
    • 没有dllimport/dllexport的累赘, 直接include头文件就能使用;
    • DLL Hell的问题也小得多, 因为Linux允许多个版本的动态库并存, 而且每个符号可以有多个版本;
    • 动态库(.so), 静态库(.a), 源码库(.cc);
  • 动态库比静态库节省磁盘空间和内存空间, 并且具备动态更新的能力(可以hot fix bug), 似乎动态库应该是目前的首选;
动态库是有害的
  • 新的库会破坏二进制兼容性;
  • 静态库也好不到哪儿去, 静态库相比动态库主要有几点好处:
    • 依赖管理在编译器决定, 不用担心日后它用的库会变, 同理, 调试core dump不会遇到库更新导致debug符号失效的情况;
    • 运行速度可能更快, 因为没有PLT(过程查找表), 函数调用的开销更小;
    • 发布方便, 只要把单个可执行文件拷贝到模板机器上;
  • 静态库的一个小缺点是链接比动态库慢, 有的公司甚至专门开发针对大型C++程序的链接器;

源码编译是王道

  • 每个应用程序自己选择要用到的库, 并自行编译为单个可执行文件;
  • 最好能和源码版本工具配合, 让应用程序只需要指定用那个库, build工具能自动帮我们check out库的源码;
  • 在目前看到的开源build工具里, 最接近这一点的是Chromium的gyp和腾讯的typhoon-blade, 其他如SCons, CMake, Premake, Waf等工具;

Mudo C++网络库第十章学习笔记的更多相关文章

  1. Mudo C++网络库第八章学习笔记

    muduo网络库的设计与实现 muduo是基于Reactor模式的C++网络库; Reactor的关键结构 Reactor最核心的是事件分发机制, 即将IO multiplexing拿到IO事件分发给 ...

  2. Mudo C++网络库第二章学习笔记

    线程同步的精要 并发有两种基本的模型: 一种是message passing(消息传递); 另一种是shared memory(共享内存); 在分布式系统中(有多台物理机需要通信), 运行在多台机器上 ...

  3. Mudo C++网络库第六章学习笔记

    muduo网络库简介 高级语言(Java, Python等)的Sockects库并没有对Sockects API提供更高层的封装, 直接用它编写程序很容易掉到陷阱中: 网络库的价值还在于能方便地处理并 ...

  4. Mudo C++网络库第十一章学习笔记

    反思C++面向对象与虚函数 C++语言学习可以看<C++ Primer>这本书; 在C++中进行面向对象编程会遇到其他语言中不存在的问题, 其本质原因是C++ class是值语义, 而非对 ...

  5. Mudo C++网络库第四章学习笔记

    C++多线程系统编程精要 学习多线程编程面临的最大思维方式的转变有两点: 当前线程可能被切换出去, 或者说被抢占(preempt)了; 多线程程序中事件的发生顺序不再有全局统一的先后关系; 当线程被切 ...

  6. Mudo C++网络库第三章学习笔记

    多线程服务器的适用场合与常用编程模型 进程间通信与线程同步; 以最简单规范的方式开发功能正确.线程安全的多线程程序; 多线程服务器是指运行在linux操作系统上的独占式网络应用程序; 不考虑分布式存储 ...

  7. muduo网络库源码学习————Timestamp.cc

    今天开始学习陈硕先生的muduo网络库,moduo网络库得到很多好评,陈硕先生自己也说核心代码不超过5000行,所以我觉得有必要拿过来好好学习下,学习的时候在源码上面添加一些自己的注释,方便日后理解, ...

  8. Mudo C++网络库第七章学习笔记

    muduo编程示例 muduo库是设计来开发内网的网络程序, 它没有做任何安全方面的加强措施, 如果在公网上可能会受到攻击; muduo库把主动关闭连接这件事分成两步来做: 如果主动关闭连接, 会先关 ...

  9. Mudo C++网络库第五章学习笔记

    高效的多线程日志 日志(logging)有两个意思: 诊断日志(diagnostic log), 常用日志库提供日志功能; 交易日志(transaction log), 用于记录状态变更, 通过回放日 ...

随机推荐

  1. 不存数据库的token验证

    不需要数据库存,纯粹通过计算来判断是否相等 {name:chuck,id:1}|自己加密方式加密后的内容 截取加密内容,反解,判断反解内容与{name:chuck,id:1}是否相同,只会耗费计算资源 ...

  2. vim学习之git for windows

    这是我在博客园的第二篇文章,今晚是在线的特殊日子,应小编的要求不想多讲,喝了点酒,感觉到压力和挑战性,抽了几根烟,现在有点飘飘欲仙的感觉.在长江大学11教的6楼,是长大在线的办公室,这个晚上总是不关灯 ...

  3. transitionEnd不起作用解决方法

    var show = function(html, className) { className = className || ""; var mask = $("< ...

  4. FastJson用法

    namespace test { class Program { static void Main(string[] args) { var zoo1 = new zoo(); zoo1.animal ...

  5. MyEclipse2017 隐藏回车换行符

    Preferences->Text Editor->Show Whitespace characters(configure visibility)->Transparency Le ...

  6. 将.csv数据导入到mysql中

    1.首先看一下我需要导入的数据: 用excel打开的时候显示: 用notepad++打开显示为: 2.使用notepad++打开改变字符集为UTF-8 3,建立表,表中的字段要和文件中的一致 3.执行 ...

  7. springboot13 发布和监听事件

    spring中的事件驱动模型Event(也叫发布订阅模式),是观察者模式的一个典型的应用 好处:业务解耦,在不影响原来业务逻辑的情况下,加入其它业务 场景: app上线后已实现用户注册功能,现需要在用 ...

  8. pycharm中创建包时加入的_init_.py文件及_all_的作用

    init__.py的主要作用是: 1. Python中package的标识,不能删除 2. 定义__all__用来模糊导入 3. 编写Python代码(不建议在__init__中写python模块,可 ...

  9. Docker 空间大小设置 - 十

    一.容器启动 默认存储大小: 1.一种在启动项 docker.service 中配置. 2.在启动项配置调用的 docker-storage 配置文件中配置: 二.Docker 容器默认启动文件: / ...

  10. Js JSON.stringify()与JSON.parse()与eval()详解及使用案例

    (1)JSON.parse函数 作用:将json字符串转换成json对象. 语法:JSON. parse(text[,reviver]). 参数:text  必须:一个有效的json字符串. revi ...