找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 2968|回复: 15

C++ 内联函数和constexpr函数可以在程序中定义不止一次,这...

  [复制链接]

16

主题

433

回帖

2536

积分

高级会员

积分
2536
发表于 2018-9-26 21:17:50 | 显示全部楼层 |阅读模式

                能定义不止一次的好处是方便你放到头文件里,放到头文件里的好处是每个include这个头文件的.c文件都能看到函数体,看到函数体的好处是编译器可以内联。内联的好处是代码变快了。另外,所有函数体定义必须一模一样,不然出了问题概不负责。constexpr自带inline属性。

当你下决心在.c文件中定义函数体的时候,自然不需要inline关键字了。而这时候也必须link相应的.o来确保找得到定义。

------------------------------ 话痨版 ------------------------------

首先来几个前置知识:

1) C和C++都有translation unit(或compilation unit)的概念:基本上编译器会一次编译一个文件,然后忘记一切,然后再编译下一个文件。哪怕你写gcc -c a.c b.c,其实和gcc -c a.c && gcc -c b.c大体上是没区别的。在最后,所有的.o文件都被linker汇总link成一个大的可执行文件。

2) static function。你可以把一个函数标记为static(也称为internal linkage),这样该函数的symbol就会被隐藏,从而该函数只存在在当前translation unit。换了下一个translation unit之后,该函数被忘得一干二净。linker也从来不知道这函数存在过。这时候你就算再定义一次上次translation已经定义过的static函数,linker也不会报redefinition错误。当然,这样代码在binary中就出现了多次。

3) 当然你也肯定知道C和C++的include的意思:在A中#include <B>就是把B的内容复制粘贴到A中#include对应的位置。

4) 编译器的内联优化就是看到你在Bar里调用Foo的时候,帮你复制一遍Foo的函数体,内嵌到Bar里去,同时消除栈操作的开销(因为代码已经被复制到“本地”了嘛,不需要跳来跳去了)。内联优化有个缺陷,就是在同一个translation unit里一定要看到函数体,所以光看到declaration是没用的。

现在考虑这么个问题:传统的在头文件中声明,在一个文件(.c)中实现函数体的方式有时执行太慢了。为什么慢呢,假设我这个函数就一行,但是函数调用的压栈传参数弹栈跳转等指令占了大部分开销,真是太不合算了。

这时候在传统C里面有两个解决方案:
1) “宏函数”。就是把本来藏在.c文件里的函数体放到一个宏里面去,当然宏也在头文件里。然后大家include头文件的时候就把宏也include走了,使用宏的时候就把这段代码一遍遍展开到所有使用的地方,消除了函数调用的开销。
2) 在编译器支持内联优化的情况下,在头文件里定义static function。任何别的.c文件,只要include了你的头文件,都对你的头文件做了一次复制粘贴,自动获得了该static function的函数体。所以在不同的translation unit里面,这些函数并不冲突,因为它们是static的。值得一提的是,这些函数体不一定一模一样。举例来说:
// a.h    #define FOO 3static int Foo() { return FOO; }// a.c#include "a.h"// b.c#undef FOO#define FOO 2#include "a.h"
在不同的translation unit里面一个Foo返回3一个返回2。

1) 的坏处很明显,宏不能解决类型检查的问题,宏是dynamic scope(变量检查环境都是调用端而非定义端的)的,宏是textual substitution,搞不好有迷之编译不通过,宏很丑,定义不带语法高亮(雾),等等。
2) 看上去很好诶,写的是真正的函数,编译器还有能力内联。其缺陷是在编译器决定不内联的时候(通常这时候函数很大),每个translation unit中都定义了一个很大的函数,造成了不必要的binary size bloat。

这时候C++之父Bjarne Stroustrup站出来了,说我们在C++里搞个inline关键字吧!这个关键字不仅编译器认识,而且编译器在没有真正内联该函数时,会通过某种方式提示linker说这个函数被标记为“可重复定义”耶 - 根据我用gcc的实验,生成的是一个weak symbol。当linker看到一个weak symbol,会把函数名写在一个小本本上。在linker最后把所有文件link到一起的时候,它会把小本本扫一遍,对于同名函数只留一个,别的函数连带函数体统统删掉。这样就解决了binary size bloat的问题。当然这只是一种典型实现方式,并非一定如此。

另外,在编译器真正内联了该函数的时候,效果就和static一样了,这也是为什么你的代码里找不到定义 - 因为linker根本看不到static函数。话虽这么说,但是他们不管这个叫internal linkage(inline specifier),因为此时linkage是个implementation detail。语言只是强调:
The definition of an inline function must be present in the translation unit where it is called (not necessarily before the point of call)

在前面提到,用include static functions的方式中include进来的函数体可能不完全一样。inline此处也提到,你要是同名函数的函数体长得不一样,我才不告诉你我要留哪一份删哪几份呢。你要是敢这么做,我不保证我的输出有意义。这个在C++里叫做ODR violation (Definitions and ODR)。编译器一次只看一个translation unit,所以通常是没法检测ODR violation的(不排除LTO还是能查的),而linker也不查,我并不清楚为什么,大概是太昂贵吧。

另外,可以感受下当年inline关键字的marketing口号:
“An Inline Function is As Fast As a Macro”(Inline - Using the GNU Compiler Collection (GCC)

顺带建议一下刷ACM-ICPC的各种坑爹oj的同学,想要速度就用宏,因为oj可能不开内联优化。。
工控课堂 www.gkket.com

0

主题

451

回帖

2972

积分

高级会员

积分
2972
发表于 2018-10-10 12:56:07 | 显示全部楼层
在遇到你之前我对人世间是否有技术大佬保有怀疑,现在我是彻底被你征服了
工控课堂 www.gkket.com

0

主题

393

回帖

2625

积分

高级会员

积分
2625
发表于 2018-12-29 00:59:49 | 显示全部楼层
感谢分享,佩服佩服!
工控课堂 www.gkket.com

11

主题

435

回帖

2643

积分

高级会员

积分
2643
发表于 2019-4-7 07:37:47 | 显示全部楼层
楼主加油,我们都看好你哦。
工控课堂 www.gkket.com

11

主题

444

回帖

2505

积分

高级会员

积分
2505
发表于 2019-4-10 14:08:01 | 显示全部楼层
绝对干货,楼主给力,支持了!!!
工控课堂 www.gkket.com

0

主题

453

回帖

2302

积分

高级会员

积分
2302
发表于 2019-4-21 18:39:43 | 显示全部楼层
真是难得给力的帖子啊。
工控课堂 www.gkket.com

0

主题

89

回帖

142

积分

新手上路

积分
142
发表于 2025-11-13 22:26:31 | 显示全部楼层
楼主太会说了,字字句句都在理
工控课堂 www.gkket.com

0

主题

165

回帖

385

积分

注册会员

积分
385
发表于 2025-11-15 12:23:06 | 显示全部楼层
水个经验,楼主加油,支持你~
工控课堂 www.gkket.com

0

主题

98

回帖

153

积分

新手上路

积分
153
发表于 2025-11-16 04:29:18 | 显示全部楼层
说得对!狠狠赞同,没毛病~
工控课堂 www.gkket.com

0

主题

80

回帖

129

积分

新手上路

积分
129
发表于 2025-11-16 11:50:27 | 显示全部楼层
浅评一下:内容优质,值得推荐~
工控课堂 www.gkket.com
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

QQ|手机版|免责声明|本站介绍|工控课堂 ( 沪ICP备20008691号-1 )

GMT+8, 2025-12-23 06:03 , Processed in 0.086577 second(s), 29 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表