C++相关问题-重复代码消除

重复代码消除

C++编译器在很多时候会产生重复的代码,比如模板,外部内联函数和虚函数表都有可能在不同的编译单元里生成相同的代码。最简单的情况就拿模板来说,模板从本质上来讲很像宏,当模板在一个编译单元里被实例化时,它并不知道自己是否在别的编译单元也被实例化了。所以当一个模板在多个编译单元同时被实例化成相同的类型的时候,必然会生成重复的代码,把这些重复的代码保留下来会存在以下几个问题:

  1. 空间浪费。
  2. 地址较易出错。有可能两个指向同一个函数的指针会不相同。
  3. 指令运行效率较慢。因为现代的CPU都会对指令和数据进行缓存,如果同样一份指令有多份副本,那么指令Cache的命中率就会降低。

一个比较有效的做法就是将每个模板的实例代码都单独地放在一个段里,每个段只包含一个模板实例。比如有一个模板函数时add(),某个编译单元以int类型和float类型实例化了该模板函数,那么该编译单元的目标文件中就包含了两个该模板实例的段,我们假设这两个段的名字分别叫.temp.add和.temp.add。这样,当别的编译单元也以int或float类型实例化该模板函数后,也会生成相同的名字,这样编译器在最终链接的时候可以区分这些相同的模板实例段,然后将它们合并入最后的代码段。
这种做法被当前的编译器所采用,GNU GCC编译器和VISUAL C++编译器都采用类似的方法,GCC把这种类似的须要在最终链接过程时合并的段叫"Link Once",它的做法是将这种类型的段命名为".gnu.linkonce.name",其中"name"是该模板函数实例的修饰后名称。VISUAL C++编译器做法稍有不同,它把这种类型的段叫做"COMDAT",这种"COMDAT"段的属性字段都有IMAGE_SCN_LNK_COMDAT(0x00001000)这个标记,在链接器看到这个标记后,它就认为该段是COMDAT类型的,在链接时会将重复的段丢弃。
对于外部内联函数和虚函数表的做法也类似,比如对于一个有虚函数的类来说,有一个与之相对应的虚函数表,编译器会在用到该类的多个编译单元生成虚函数表,造成代码重复,外部内联函数,默认构造函数,默认拷贝构造函数和赋值操作也有类似的问题,它们的解决方法基本跟模板的重复代码消除类似。
这种方法虽然能够基本上解决代码重复的问题,但还是会存在一些问题,比如相同名称的段可能有不同的内容,这可能由于不同的编译单元使用了不同的编译器版本或者编译优先选项,导致同一个函数编译出来的实际代码有所不同,那么这种情况下链接器可能会做出一个选择,那就是随意选择其中任何一个副本作为链接的输入,然后同时提供一个警告信息。

函数级别链接

由于现在的程序和库通常都非常庞大,一个目标文件可能包含成千上万个函数或变量,当我们须要用到某个目标文件中的任意一个函数或变量时,这须要把整个的链接起来,也就是说那些没有用到的函数也被一起链接了起来。这样的后果是链接输出文件会变得很大,所有用到的没用到的变量和函数都一起塞到了输出文件中。
VISUAL C++编译器提供了一个编译选项叫函数级别链接,这个选项的作用就是让所有的函数都像前面模板函数一样,单独保留到一个段里面。当链接器须要用到某个函数时,它就将它合并到输出文件中,对于那些没有用到的函数则将它们抛弃。这种做法可以很大程度上减少输出文件的长度,避免了空间浪费,但是这个优化选项会缓慢编译和链接过程,因为链接器须要计算各个函数之间的依赖关系,并且所有函数都保存到独立的段里,目标函数的段的数量大大增加,重定位过程也会因为段的数量的增加而变得复杂,目标文件随着段数量的增加也会变得相对较大。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值