在以前面试的时候曾被问道一个问题,cpp中inline关键字的作用是什么,当时只记得inline的语义在c++17标准中进行了修改,而修改后的却忘了,今天在实现单例模式的时候遇到了和inline有关的bug,现在记录一下。

内联

通常在各类教程中会提到,inline的作用是让编译器内联展开所修饰的函数,从而节省跳转函数的开销,在代码中可以测试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

__declspec(noinline) int TestInlineFunction(int a, int b) // __declspec(noinline)是msvc的一个标记,声明编译器永远不要内联这个函数
{
return a + b;
}

int main() {

int ans = TestInlineFunction(1, 2);
std::cout << ans << std::endl;
return 0;
}

编写一段如上的代码,我们声明了永远不要内联这个函数避免编译器的优化。打开反汇编可以看到汇编中有 call TestLineFunction指令,也就是进行了函数跳转,并没有内联。

现在再删去__declspec(noinline),重新编译(需要开启优化)并查看反汇编,call TestLineFunction指令没有了,编译器发现了这个函数可以直接原样复制到main函数中而不需要多余的指令,这个复制函数的动作也就是内联。

现在你应该注意到了,上述的代码中我们并没有使用inline,只是去掉了禁止内联的声明,编译器就自动帮我们进行了内联,既然如此inline关键字的作用是什么?

cpp17

inline关键字最开始是在C99标准中提出,原文提到The intent of the inline specifier is to serve as a hint for the compiler to perform optimizations –inline function specifier,也就是提示(建议)编译器对函数进行内联,既然这不是强制性的要求内联,那么这个关键字的用处其实并不明显,比如上面的例子中是否添加inline并不会修改编译器的行为。

而在cpp17中为inline关键字增添了新的含义,使其可以修饰变量,也就是提供了内联变量,其目的主要是为了解决static关键字在不同翻译单元会复制变量的问题,接下来给出一个代码示例

1
2
3
4
5
6
7
8
9
10
// test.h
#pragma once
#include <string>

const char* var = "var";
static std::string staticVar = "static";
inline std::string inlineVar = "inline";

void funStatic();
void funInline();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// test.cpp
#include "test.h"
#include<iostream>


void funStatic()
{
std::cout << "header1.h:" << staticVar << std::endl;
}

void funInline()
{
std::cout << "header1.h:" << inlineVar << std::endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// main.cpp
#include "test.h"
#include<iostream>

int main()
{
staticVar += " in main";
inlineVar += " in main";

std::cout << "main.cpp :" << staticVar << std::endl;
std::cout << "main.cpp :" << inlineVar << std::endl;

funStatic();
funInline();

return 0;
}

编译运行以上代码,会发现错误

这是因为test.cpp和main.cpp都include了test.h变量,const char* var同时存在于这两个文件,当链接时链接器会发现var这个变量被定义了两次,所以报错,在以前的cpp中,有两种解决方法:

1.在某一个源文件中正常声明变量const char* var并且初始化它,而在其他源文件中使用extern const char* var进行声明,但是这种方式无法在头文件中使用,或者说头文件无法被多个源文件引用。

2.使用static const char* var定义变量,这样就不会出现重定义错误,但是这样会在每一个源文件中保留一份单独变量,即每个源文件中都有同样的一个叫var的变量。

inline就是为了解决这个问题而出现的,inline修饰的变量在所有源文件中都是同一个变量,运行上述的示例可以得到如下结果:

可以看到main函数中的输出都正确添加了in main的字符串,而调用函数,也就是test.cpp中的两个输出却不同,static变量并没有添加in mian,而inline变量正确添加了。

即test函数中输出的static变量是被复制出的单独的一个变量,和main.cpp中被添加in main的不是同一个变量,而inline在两个文件中都是同一个变量。

总结而言:

static修饰的变量会在不同的源文件中单独保留一份

inline修饰的变量在全局只有同一份,注意inline无法单独修饰局部变量。

此外static和inline可以同时使用,也就是static inline int var,这样修饰的变量可以在局部函数中声明,并且同时保留了static和inline的特征,也就是生命周期静态,而全局只保留同一份变量。