C++的内部链接、外部链接及inline函数的探讨

10 篇文章 0 订阅

先是几个概念:
编译单元:
首先明确,只有源文件(.cpp/.c)才能被编译器编译。预处理器首先递归包含头文件,形成一个含所有必要信息的单个源文件,此源文件就是一个编译单元。
我可将其理解为被预处理后,包含头文件的.cpp文件。

内部连接:
如果一个编译单元(.cpp)内的名称对编译单元(.cpp)来说是局部的,在链接的时候其他的编译单元无法链接到它且不会与其它编译单元(.cpp)中的同样的名称相冲突。
如被定义为inline的函数(关键字inline须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前不起作用),static函数(关键字static只要放在函数声明前就可以了),还有(直接在类中定义的函数一般会被处理成inline)。

外部连接:
如果一个编译单元(.cpp)内的名称对编译单元(.cpp)来说不是局部的,而在链接的时候其他的编译单元可以访问它,也就是说它可以和别的编译单元交互。如全局变量全局函数等。

有个a.cpp:

#include <stdio.h>

static void func(void)
{
	printf("call func()\n");
}

//inline void func(void)
//{
//	printf("call func()\n");
//}

以及一个main.cpp:

#include<stdio.h>

extern void func(void);

int main()
{
	func();
	return 0;
}

编辑结果:

由于a.cpp内的两个函数都属于内部连接,即便main.cpp中声明了func()为extern,依然无法连接。
此时,如果main.cpp想使用func(),必须在main.cpp内加上

#include "a.cpp"

这样虽然显得很蠢,却也道出了inline函数为什么要在头文件内定义的原因:

1. inline类似与宏,在预编译期间展开。各个.cpp文件包含头文件即是为了定义一份,在其调用之处展开以减少函数调用的开销(调用前先保存寄存器,并在返回时恢复,复制实参,程序还必须转向一个新位置执行), 展开后实际各个.cpp文件会各自持有一份inline函数代码实例。
如果在.cpp文件中定义,也是可以的。只不过每个使用这个"inline函数"的.cpp文件都要包含这个.cpp,或者在每个.cpp文件内都定义这么一个"inline函数":这样毫无意义。

2. 在一个a.h文件中定义一个非inline retAobj函数,供a.cpp main.cpp多个文件调用。编译时不报错而链接时将报错

/tmp/cc0hqPdZ.o: In function `retAobj()':
a.cpp:(.text+0x0): multiple definition of `retAobj()'
/tmp/ccZ5WVA1.o:main.cpp:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

这是因为retAobj函数属于外部链接,头文件在两个cpp文件展开后,链接器认为同一个retAobj有两份定义,导致名称冲突而报错。指定为inline后即便名字相同,链接器认为是两个cpp文件内有两个不同的retAobj函数没问题。这是h文件中指定函数inline的另一个原因。一般而言,h文件只提供声明式而非定义(出于降低编译依赖,或者第三方类库隐藏实现等原因),inline则是一个特例。

tips:More Effective C++的条款26提到,

class Printer{
...
friend Printer& thePrinter();
...
}

Printer& thePrinter()
{
    static Printer p;
    return p;
}

其中非成员函数thePrinter()虽然简短,但不能声明为inline的原因:
static Printer p是个local static对象(local static对象在函数调用时构造;非local static对象在main()开始前就已被构造)
thePrinter()如果是个inline函数,编译器会对该inline展开多份,也导致复制了多份static Printer p变量,与初衷违背。

但是经尝试,无论thePrinter()是否inline,都不会导致其内的static Printer p复制多份。最新C++标准规定,extern inline(不写 static 默认就是 extern )里的static变量一定是同一个对象。所以这里的thePrinter()属于external inline。当然了,extern inline还是inline函数,函数还是会被展开多份而不冲突,只不过里面的static对象确保是同一个。

参考:点击打开链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值