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. Hudson持续集成管理平台搭建

    IP: 10.0.70.106  8G 内存 (Hudson 多 个 工程 在 同 时 构建 的情况下 比 较耗内存) 环 境: CentOS 6.5 . JDK7 注:Hudson 只是一个持续集成 ...

  2. JWT认证

    1.什么是JWT Token JWT(Json Web Tokens) 是一个开放标准(RFC 7519),它定义了一种简洁,自包含,JSON 对象形式的安全传递信息的方法.JWT常用在 Web 应用 ...

  3. mysql关联模糊查询他表字段

    如下:订单表关联了用户的id(多个),要根据用户名模糊查询订单信息,但是订单表只有id.创建视图用不着,咱也没权限.于是如下 SELECT * FROM ( SELECT cu.id AS 'id', ...

  4. Chrome DevTools: Export your raw Code Coverage Data

    The Code Coverage tool visually shows you which lines of code in your CSS and JavaScript are used an ...

  5. HDU 1098(条件满足 数学)

    题意是问是否存在非负整数 a,使得任取非负整数 x,f(x) 能够被 65 整除,其中 f(x) = 5*x^13 + 13*x^5 + k*a*x,如存在,输出 a 的最小值,如不存在,输出 no. ...

  6. redhat开启端口

    设置端口:sudo iptables -I INPUT -p tcp -m tcp --dport 80 -j ACCEPT 或 /etc/init.d/iptables -I INPUT -p tc ...

  7. 生成表结构数据库文档sql语句

    CREATE PROCEDURE [dbo].[生成表结构数据库文档]ASBEGIN -- SET NOCOUNT ON added to prevent extra result sets from ...

  8. js 数组拷贝与深拷贝

    1.对于普通数组(数组元素为数字或者字符串) var _testCopy = [1,2,3].concat();//拷贝数组(浅拷贝) 2.对于对象数组 (深拷贝) //形如var _objArr=[ ...

  9. Mysql宽字节注入(转)

    尽管现在呼吁所有的程序都使用unicode编码,所有的网站都使用utf-8编码,来一个统一的国际规范.但仍然有很多,包括国内及国外(特别是非英语国家)的一些cms,仍然使用着自己国家的一套编码,比如g ...

  10. 20155324 2016-2017-2 《Java程序设计》第3周学习总结

    20155324 2016-2017-2 <Java程序设计>第3周学习总结 教材学习内容总结 定义类 要产生对象必须先定义类,类是对象的设计图,对象是类的实例.类定义时使用class关键 ...