一个 inline 函数引发的葫芦案

一个同事在检查另外一个同事的代码时,突然发现一个 inline 函数在不同的 cpp 文件里面都有定义,而且,在另外一个cpp里面也引用了这个inline函数。示意代码如下:

// a.h
inline extern void foo();

 

// a.cpp
#include <cstdio>
#include "a.h"
inline void foo(){
    printf("a.foo()/n");
};

// b.h
inline extern void foo();

 

// b.cpp
#include <cstdio>
#include "b.h"
inline void foo(){
     printf("a.foo()/n");  // 如果这里改一下,地球将会怎样 ?
};

// c.cpp
#include "a.h"
int main(){
     foo();
     return 0;
};

一开始觉得很奇怪,怎么定义了2个一摸一样的inline函数呢 ? 而且在VC下面编译的时候居然没有报错,更加神奇的,是在链接的时候也没有报错。。。

于是乎,查C++标准,查各种文档,不断地试验,终于得知了为什么能够编译和链接:

1、当 inline 函数被声明为 extern 或者在定义该inline函数的翻译单元(TU)之外也有对该函数的引用的时候,该函数就会被作为一个特别的函数来生成代码(类似于一般的函数),而不是一段嵌入的代码(最简单的inline函数那种)。这就解释了为什么能够编译成功。

2、在编译成功后要进行链接啊,比如上面的例子,c.cpp里面的foo引用要跟a.cpp或者b.cpp里面的foo定义链接啊,按照上面1的情况,会有2个foo的定义,这会导致“定义歧义”的错误,但是为什么这里没有报错呢 ?大家估计可以猜得到,肯定是链接器只链接了其中一个版本,比如a.obj或者b.obj里面得版本。这个是什么机制呢 ?

3、答案就在前面第1点里面,当被作为一个特别的函数生成目标代码的时候,这个“特别的函数”的特别地方就是它的目标代码被标志为“延迟链接”,同时被被标志为在重定位的时候“pick any”,这就意味着我们所定义的foo函数是延迟链接,最后从2个foo定义里面取一个进行重定位,生成可执行文件。这就解释了为什么能够链接成功而不报错误。

4、既然是“pick any”,如果没有明确的链接顺序指定给链接器(linker),则很容易产生预期结果和实际结果不一致的情况(linker充当一下葫芦官),比如,上面的例子,你用 cl  /out:c.exe a.cpp   c.cpp    b.cpp得到的 c.exe 和 cl  /out:c2.exe   b.cpp   c.cpp  a.cpp 得到的 c2.exe 是不一样的,可以试着修改一下 b.cpp 里面的 foo 函数,就知道修改文件的次序也会导致结果的不同。

5、这个例子是一个典型的“code of bad smell ”,这些代码导致修改了foo函数的修改不能如实地按照预期在最终的可执行文件里面得到体现,导致代码和结果的不一致,如果栽在这样的问题当中,估计无论是谁都会很郁闷。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值