【C++入门】内联函数

简介: 【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
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。


1.png


我们发现这里出现call Add(07FF76C5E13CAh),发现Add函数并没有称为Inline。这就是因为默认Debug模式下内联不会起作用。所以我们要调一调属性:


2.png


然后依然是在Debug模式下进行调试,请看:

3.png


现在我们发现并没有出现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函数是内联函数吗?请看:


4.png

我们可以看到这里并没有把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);


运行结果如下:

5.png

这里编译没有出现问题,而是链接出现了问题,即链接不上,为什么这里明明有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();



运行结果如下:

6.png

好了,以上就是C++中的内联函数的介绍。

到这里啦,再见各位!!!

目录
相关文章
|
1月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
38 2
C++入门12——详解多态1
|
1月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
79 1
|
1月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
23 0
|
1月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
24 0
|
1月前
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
1月前
|
编译器 Linux C语言
【C++入门(上)】—— 我与C++的不解之缘(一)
【C++入门(上)】—— 我与C++的不解之缘(一)
|
1月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
30 0
|
1月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
32 0
|
1月前
|
存储 算法 C++
C++入门10——stack与queue的使用
C++入门10——stack与queue的使用
40 0
|
1月前
|
存储 C++ 容器
C++入门9——list的使用
C++入门9——list的使用
18 0