什么是内联函数
内联函数概念:
内联函数就是以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 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 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 using namespace std; //inline void func(int i); inline void func(int i) { cout << i << endl; } void test();
运行结果如下:
好了,以上就是C++中的内联函数的介绍。
到这里啦,再见各位!!!