您的位置:首页 > 编程语言 > C语言/C++

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

2018-01-05 21:11 357 查看
先是几个概念:
编译单元:

首先明确,只有源文件(.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文件都要包含这个.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变量,与初衷违背。

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


参考:点击打开链接
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: