0
点赞
收藏
分享

微信扫一扫

C++之内联函数(3千字长文详解!)

C++之内联函数详解

内联函数的概念

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调 用建立栈帧的开销,内联函数提升程序运行的效率。

为什么有内联函数?

在详细的讲解内联函数之前我们要思考一下为什么会存在内联函数?下面我们以快排算法为例,为大家讲解一下为什么要存在内联函数。

不了解快排的也不需要担心,我们只需要知道在快排中,我们需要频繁的调用swap函数

当我们要使用快排(qsort)时,我们要调用swap函数,而调用函数是需要建立栈帧的!所以数据量一大!我们就要不断的建立栈帧!而频繁建立栈帧这件事本身也是有性能消耗的!

所以为了进一步的优化性能我们该结局和这个问题呢?

在c语言中,我们可以使用==宏函数==或者直接==写在qsort函数里面==!

直接写在里面就不需要调用函数建立栈帧!但是直接写在里面就失去了复用性!

所以为了具有更高的复用性使用宏函数是更好的选择!

因为宏函数的本质是替换,会在预编译阶段就进行替换也相当于直接写在里面!

但是宏函数本身也是有很多缺点的!

  1. 不能调试!这就是一件非常麻烦的事情,也就是一旦出错我们可能要花很长时间才能发现问题在哪!
  2. 没有类型安全的检查
  3. 宏函数很容易写错!

所以c++为了解决这个麻烦提出来内联函数这个概念!它解决了宏函数的所有缺点!又保留了它的所有优点!

内联函数的使用

inline int swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}//这就是内联函数!

内联函数只会在需要的时候进行展开!

什么叫展开?

int add(int x,int y)
{
     return x+y;
}
int main()
{
    add(1,3);
    return 0;
}

这是不加inline的函数在反汇编情况下

image-20220921171921575.png

内联函数的特点

  1. 内联函数只是对编译器发出的一个建议!编译器可以选择忽略这个请求!

    那么什么情况下不适合使用内联呢?——递归和长函数的时候都不适合使用内联!

    我们下面举个例子

    假设一个func函数有在汇编中30个指令,而这个函数有10000个调用的地方!

    那么请问展开和不展开的指令数分别是多少?

    我们已知不展开的时候编译器会发出==call指令==去调用函数建立栈帧!10000个调用的地方就说明我们要调用10000次,说明==call了一万次==,调用完后再回来,但是func本身所含有的的指令数是不变的!所以总的指令数为10000+30 = 10030个

image-20220921174909859.png

如果我们进行展开呢?那我们10000个调用的地方就要展开10000次,那么一次30个指令数,一共有10000*30 = 30w个指令数!

==这种现象就被我们称为代码膨胀!==就是无用重复的代码太多了!

而这会导致什么后果?——==可执行程序大小会膨胀变大==!就是我们常说的安装包!所以内联函数是不可以随便乱用的!

现在我们回头看递归和长函数,长函数一旦多次展开就导致代码膨胀!而递归如果深度一深,也会疯狂的进行展开!同样会导致代码膨胀!

所以inline真正适合的是需要多次调用的,非递归的,函数规模较小(函数很短)的函数。

可以用于减少开销,提高程序运行效率!而目标文件大小又不会过度膨胀

所以为了防止内联被乱用,内联被使用的使用要经过编译器的判断!如果函数过长或者是个递归,编译器可以忽略这个请求!

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会 用函数体替换函数调用,缺陷:可能会使目标文件变大——就是上面所说的代码膨胀!优势:少了调用开销,提高程序运 行效率。

  2. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到

    //a.h中
    inline test(int x,int y);
       
    //a.cpp中
    inline int test(int x, int y)
    {
    	return x + y;
    }
       
    //test.cpp
    int main()
    {
    	test(1,2);
       
    	return 0;
    }
    

    image-20220922155711012.png

    我们会得到这样的报错!就好像没有被声明一样!

    先看==没有加inline==的样子

    当我进行预编译的时候.h文件会进行展开!变成下面的样子!然后执行程序

    //test.cpp
    int test(int x,int y);
    int main()
    {
    	test(1,2);
       
    	return 0;
    }
    

    执行程序后此时我们看反汇编

image-20220922162337734.png

我们可以看到当调用test函数的时候旁边是有一堆数字的,这一堆数字就是他的地址!就是通过这个地址我们才找到这个函数!

但是如果我们加入了inline这个位置就变成了一个?没有了地址!完全找不到了!

相当于我们只有一个声明!没有地址!只有声明的话编译器是可以通过的!但是没有地址链接器就无法进行链接!而一般来说这个地址是放在符号表里面的!链接器通过这个名字去符号表里面去找相应的地址!然后对不同的源文件进行链接!

那为什么加了inline就找不到了?==因为对于内联函数来说,它的作用是在需要的时候进行展开!没有人会去call内联函数的地址!==所以 内联函数根本不会去生成什么建立栈帧的指令,也没有必要把他的地址放进符号表里面!

这就导致了一旦内联函数声明和定义分离开来,就会导致链接错误!

所以最好的办法将声明的定义放在同一个头文件里面!

那么这里又有一个小问题?

我们都知道inline只是一个建议,当函数过长或者为递归的时候,编译器可以忽略这个建议,那么问题来了?如何我们把一个很长的函数(编译器一定会忽略这个建议)前面加个inline 然后声明和定义分离,那是不是就可以通过了?

//a.cpp中
inline int test(int x, int y)
{
	int a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
	a = x + y;
}
   
//a.h中
inline test(int x,int y);
   
   //test.cpp
int main()
{
	test(1,2);

   	return 0;
}

image-20220922155711012.png

举报

相关推荐

0 条评论