本章来聊一聊C++的创作者"本贾尼"大佬,为什么要创作出内联函数,以及内联函数的定义和内联函数的实现机制等等。
话不多说,让我们直入主题!💖💖💖
请大家先看下面的代码:
#include<stdio.h>
int Add(int x, int y)
{
return x+y;
}
int main()
{
int ret = Add(10, 20);
//假设下面有很多地方都要用到Add函数
return 0;
}
假设Add函数在主函数中有非常多的地方要使用到,我们的这种写法是最好的吗?
其实,我们这种写法并不好。从函数调用的效率来看,每当我们调用一次函数就要在栈区上开辟一块空间用作函数栈帧,等函数调用完之后,栈帧就会被销毁。但是在栈区上开辟空间以及栈帧的销毁都是会浪费时间资源的,更何况同一个函数调用很多次的情况。
那我们该怎么做呢?有一个办法就是将这个函数以一种绕开编译器在调用函数时会开辟函数栈帧的做法——“宏函数”。
#include<stdio.h>
//宏函数的写法
#define Add(x,y) ((x)+(y))
int main()
{
int ret = Add(10, 20);
//假设下面有很多地方都要用到Add函数
return 0;
}
宏函数的工作原理就是,在编译处于预处理阶段时,就会在使用宏函数的地方将对应的代码给替换进去。这样就相当于只是一个代码段了,而不是一个函数。
但是宏函数的写法实在是太容易出错了,而且使用宏函数还有以下的缺点:
了解了上述场景之后,我们就来了解一下C++的"祖师爷"是如何攻克这个难关的。
C++的"祖师爷"也发现了这个问题,于是他就创造出了一个函数"内联函数",这个函数能够完美的实现上述宏函数的功能和解决和宏函数的缺点。
从形式上看:以关键字inline
开头的函数,叫做内联函数。
从功能上看:C++编译器在调用内联函数的地方展开(函数体对应的内容),没有函数调用的消耗,提高效率。
内联函数的写法:
//在函数定义的开头加一个inline关键字
inline int Add(int x, int y)
{
return x + y;
}
如果在上述的函数增加了inline关键字,在编译期间编译器会用函数体替换函数的调用。
那我们该怎么查看这个现象呢?可以通过查看汇编代码,来判断!!!
(VS)查看方式:
按我们就得更改一下Debug下的配置信息: 第一步:点击项目文件夹,选择C/C++,之后点击常规,在调试信息格式中选择程序数据库模式。
第二步:继续在C/C++选项卡下,选择优化,在内联函数拓展这块选择只适用于__inline(/0b1)
(这里只是举个例子) 普通函数的汇编代码:
内联函数的汇编代码:
对比一下内联函数和普通函数的汇编代码的区别。可以看到的是对于普通函数的调用编译器是根据call指令来执行的。而对于内联函数来说,C++编译器则是直接将函数体里面的内容转换成汇编代码替换到了函数调用的地方。
1.🍉 inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势 :少了调用开销,提高程序运行效率。 2. 🍉inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。 3. 🍉inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
f(10);
return 0;
}
// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl
//f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用
本文讲解了内联函数,内联函数是否能够成功实现具体取决于编译器的做法,我们只是给编译器提一个意见。还有一个重要的点是我们定义和声明内联函数时,得两边都用inline,否则,因为inline的作用会使得函数的地址变成代替函数体的代码段的地址,在链接阶段会因为找不到这个函数而报错!
好了,本文就讲到这里了。如果觉得本文好不错的话,麻烦给偶点个赞吧!!