个人主页:平行线也会相交
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创
收录于专栏【C++之路】
目录
- 什么是内联函数
- 内联函数特性
什么是内联函数
内联函数概念:
内联函数就是以inline修饰的函数叫做内联函数,编译时会在调用内联函数的地方展开,没有函数调用占用建立栈帧的开销。
我们知道函数的调用是有消耗的,比如函数调用会建立栈帧(在栈上开辟一块空间)。
比如:
int Add(int x, int y)
{
return (x + y) * 10;
}
int main()
{
for (int i = 0; i < 10000; i++)
{
cout << Add(i, i + 1) << endl;
}
return 0;
}
上述代码中我们建立了10000个Add函数的栈帧
,在C语言中可以使用宏函数来解决建立如此多的函数栈帧。C语言的解决方式是这样的,即:#define Add(x,y) ((x)+(y))
,一定要注意x
和y
是一个表达式,所以C语言宏函数的这种解决方式其实是容易出错的,只要稍微不注意的话就会很容易出错而且宏函数不可以进行调试;同时宏函数的一个优势就是不需要建立栈帧,提高了调用效率
。而对于C++而言,为了解决C语言的这种缺陷,C++提供的解决方案就是内联函数
。
内联函数的关键字是
Inline
。
内敛函数使用起来也是非常的方便。就不如说上面代码中我们调用了10000次Add函数
,下面请看内联函数使用样例:
inline int Add(int x, int y)
{
return (x + y);
}
内联函数相对于C语言宏函数的解决方法而言就显得非常有优势:
1.不需要建立函数栈帧。
2.可读性强,且不易出错。
3.可以调试
4.使用起来简单。。
然而并不是所有的函数都是可以使用内联函数的。内联函数只适用于短小的且频繁调用的函数。太长的函数如果使用内联函数的话会导致代码屏障
。
所以我们并不是要把所有的函数都弄成为内联函数,内联函数使用于短小而频繁调用的函数。如果函数比较长、又或者是递归函数不要让其称为内联函数。
内联函数特性
1.内联函数会在编译阶段用函数体替换函数调用。缺陷是可能会使可执行程序(或者目标文件)变大,优势就是减少了函数调用的开销,提高了运行效率。
2.内联函数只是编译器给我们的一个建议,最终函数是否会成为内联函数取决于编译器自己。如果我们把递归函数、比较长的函数加上inline
的话就会被编译器否决掉。我们可以这样理解,内联函数只是我们向编译器发出的一个请求,然而编译器可以接收这个请求,当然也完全可以忽略这个请求。
3.内联函数不建议声明和定义分离,如果分离的话就会导致链接错误,因为inline
被展开,此时就没有函数地址了,链接就会找不到。
现在有一个func函数
,这个func函数
经过编译后有50行
指令,倘若在某个项目中总共需要调用10000次func函数
。来看看是否使用内联函数的区别。
情况1:如果
func函数
不是内联函数:总共需要指令10000+50
行,每次调用都是直接跳转到func函数去执行func函数(即call func(0x11223344)
),每次跳转过去执行的是一样的,所以是+
而不是*
。
情况2:那如果func函数
是内联函数,要把这50
行指令展开放到调用的地方,即需要10000*50
行指令。这样就会导致可执行程序变大
。
我们以下面这段代码为例,看看如果加上inline
会如果,如果不加inline
又会如何。
在这之前我们首先要知道:
在Debug模式下,需要我们对编译器进行设置,否则内联函数不会展开,因为默认在Debug环境下不会对代码进行优化。
#include<iostream>
using namespace std;
inline int Add(int x, int y)
{
return (x + y);
}
int main()
{
for (int i = 0; i < 10000; i++)
{
cout << Add(i, i + 1) << endl;
}
return 0;
}
我们来看一下编译器是否会支持我们把
Add函数
弄为Inline
。
我们发现这里出现
call Add(07FF76C5E13CAh)
,发现Add函数
并没有称为Inline
。这就是因为默认Debug模式下内联不会起作用。所以我们要调一调属性:
然后依然是在Debug模式下进行调试,请看:
现在我们发现并没有出现call Add
,因为Add函数展开了,此时Add函数就是内联函数
。
现在我们改一下Add函数的内容(但依然加上Inline
)使得编译器认为Add函数
不应该是内联函数,我们把Add函数
改成这样,请看:
inline int Add(int x, int y)
{
cout << "abca" << endl;
cout << "abca" << endl;
cout << "abca" << endl;
cout << "abca" << endl;
cout << "abca" << endl;
cout << "abca" << endl;
cout << "abca" << endl;
return (x + y);
}
我们判断一下此时虽然我们在
Add函数
前加上了Inline
,但此时编译器真的认为Add函数
是内联函数吗?请看:
我们可以看到这里并没有把Add函数
进行展开,故Add函数
此时并不是内联函数,编译器认为Add函数太长了
,不应该把Add函数
弄成内联函数,否则就可能出现代码屏障,进而导致最后的可执行程序(或者目标文件)变得非常大。就算我们在Add函数
前加上Inline
也依旧不会起作用。
我们来看看内联函数的特性3:内联函数不建议声明和定义分离,如果分离的话就会导致链接错误,因为
inline
被展开,此时就没有函数地址了,链接就会找不到。
这里举个例子:
//main().cpp
#include"func.h"
int main()
{
func(100);
return 0;
}
//func.cpp
#include"func.h"
void func(int i)
{
cout << i << endl;
}
//func.h
#include<iostream>
using namespace std;
inline void func(int i);
运行结果如下:
这里编译没有出现问题,而是链接出现了问题,即链接不上,为什么这里明明有func函数
的定义却找不到呢?
程序在进行编译之后会进行链接,因为只有声明没有定义,但是声明的时候发现func函数
是一个内联函数,那好,既然是内联函数那就展开就好了,但是问题又来了,我们想展开这个内联函数却又发现展不开,因为这里只有声明,于是编译器只能call函数func的地址
,于是拿着修饰函数名?func@@YAXH@Z
去符号里找发现又找不到,因为内联函数不会生成符号表,更不会有地址(内联函数认为在需要调用自己的时候直接展开了)。故在链接阶段就找不到函数func
的定义。所以内联函数声明和定义不能进行分离,还是那句话:内联函数不会生成符号表,更不会有地址(因为内联函数认为自己会直接进行展开,根本不需要被call
)。所以链接的时候找不到函数func
的定义,就直接报错了。
我们应该这样写内联函数(不要声明和定义分离):
//main().cpp
#include"func.h"
int main()
{
func(100);
test();
return 0;
}
//func.cpp
#include"func.h"
void test()
{
func(100);
}
//func.h
#include<iostream>
using namespace std;
//inline void func(int i);
inline void func(int i)
{
cout << i << endl;
}
void test();
运行结果如下:
好了,以上就是C++中的内联函数的介绍。
到这里啦,再见各位!!!