C++相关问题
C++的一些语言特性是必须由编译器和链接器共同支持才能完成。主要就是两方面,一个是C++是的重复代码消除,一个是全局的构造和析构。
重复代码消除
C++在很多时候会产生重复的代码,比如模板、虚函数表等都可能在不同的编译单元生成相同代码。这样就会造成以下问题:
空间浪费:如果几百个编译单元同时实例了同一个模板,那么在最后链接的时候必然会消除相同的模板函数,否则程序就会像个200斤胖子。
地址较容易出错:可能两个指向同一函数的指针会不相同
指令运行效率低:如果同一份指令有多个副本,那么指令命中率就会降低。
解决上述有效的方法就是把每个模板的实例代码都单独的放在一个段里面,每个段都只包含一个模板实例。这样链接器名称相同的段(不同目标文件生成相同段名的段)的话就会区分这些模板实例段,然后合并入最后的代码段。
但是这样也有一个问题,就是不同目标文件在使用不同的编译选项可能会生成相同段名不同函数的段,在这种情况下,链接器会发出一个警告信息,并且任选其一。
哦,对了对于函数来说也使用这样的方法(函数级别链接)来减少输出文件冗余。
全局构造和析构
首先思考一下,我们都说main是C/C++程序开始执行的入口,那么全局对象的构造又是在何时进行的呢?
假设你是一名剑客,你使用剑招的前提是不是要有一把剑。同理,在main函数之前,程序就已经开始执行,包括初始化进程执行环境,如堆分配初始化,线程子系统等,全局对象的构造也是在这一时期。而析构则是在main函数之后。
在linux下一班程序的入口是_start,这是程序初始化部分的入口,存在于目标函数的.init段
(还记得吧,在目标文件详解我介绍过)。与之对应的还有一个.fini段
,这里存储着需要在main函数之后执行的函数,比如全局析构函数。
C++与ABI
首先我们要了解什么是ABI
ABI是二进制层次的应用程序接口,主要要和源代码级别的应用程序接口API有所区分。
之所以提出ABI这个概念就是因为有这样一个问题存在:是否存在着不同编译器编译出来的目标文件是不能够相互链接的呢?
答案是肯定的
因为如果两个目标文件能够链接,那么它们必须有同样的格式(还记得linux用的ELF和window下的PE么,我在目标文件详解中介绍过)、有相同的符号修饰标准(符号详解)、变量的内存分布方式相同等,同时上述几个要求就是ABI。
这个问题的解决只能等到未来,看看是否有人能一统江湖了。
静态库
库可以看成一个目标文件的集合,即一个多个目标文件组合成的压缩包。
静态库和动态库是两种共享程序代码的方式,它们的区别是:
静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系;
动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
所以说,静态库和动态库就可以看做是API。但是在各个操作系统中,API各不相同。
比如说printf函数,在linux就是write的系统调用,在windows下则是WriteConsole的系统调用
参考文献
[1] 俞甲子 石凡 潘爱明.程序员的自我修养.电子工业出版社,2009.4.