【C++】内联函数分析

一、为什么要使用内联函数:

  当程序执行函数调用指令时,CPU存储函数调用之后的指令的存储器地址,复制函数的参数在堆栈上,最后将控制转移到指定的函数。然后,CPU执行功能代码,将功能返回值存储在预定义的存储器位置/寄存器中,并将控制返回给调用功能。如果函数的执行时间小于从调用函数到被调用函数(被调用者)的切换时间,则这可能成为开销。对于大型和/或执行复杂任务的函数,与函数运行所花费的时间相比,函数调用的开销通常是微不足道的。但是,对于常用的小函数,进行函数调用所需的时间通常比实际执行函数代码所需的时间多得多。小功能的开销是因为小功能的执行时间小于切换时间。

   C ++提供了一个内联函数来减少函数调用开销。 内联函数是一个在调用时按行扩展的函数。 当调用内联函数时,内联函数的整个代码在内联函数调用点插入或替换。 此替换由C ++编译器在编译时执行。 内联函数如果很小则可以提高效率。

  内联函数具有以下优点:
1)不会发生函数调用开销。
2)当调用函数时,它还节省了推送/弹出变量在堆栈上的开销。
3)它还节省了函数返回调用的开销。
4)内联函数时,可以使编译器对函数体执行特定于上下文的优化。 对于正常的函数调用,这种优化是不可能的。 通过考虑调用上下文和被调用上下文的流程,可以获得其他优化。
5)对于嵌入式系统,内联函数可能很有用(如果它很小),因为内联可以产生比函数调用前导码和返回更少的代码。

内联功能缺点:
1)来自内联函数的添加变量消耗额外的寄存器,如果要使用寄存器的变量数增加,则在内联函数之后,它们可能会在寄存器变量资源利用率上产生开销。这意味着当在函数调用点替换内联函数体时,函数使用的变量总数也会被插入。因此,用于变量的寄存器数量也将增加。因此,如果在函数内联变量数量大幅增加之后,它肯定会导致寄存器利用率的开销。

2)如果使用太多的内联函数,那么二进制可执行文件的大小将会很大,因为相同代码的重复。

3)太多的内联也会降低指令缓存命中率,从而降低从缓存内存到主内存的指令获取速度。

4)如果有人更改了内联函数中的代码,则内联函数可能会增加编译时间开销,因此必须重新编译所有调用位置,因为编译器需要再次替换所有代码以反映更改,否则它将继续使用旧功能。

5)内联函数可能对许多嵌入式系统没用。因为在嵌入式系统中,代码大小比速度更重要。

6)内联函数可能会导致颠簸,因为内联可能会增加二进制可执行文件的大小。内存中的颠簸会导致计算机性能下降。

 

 内联函数的几个特点

1.C++编译器可以将一个函数进行内联编译
2.被C++编译器内联编译的函数叫做内联函数
3.C++编译器直接将函数插入函数调用的地方
4.内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)

注意:C++编译器不一定满足函数的内联请求!

 

 C ++中几乎不需要宏:

  预处理器直接在宏代码中替换所有宏调用。建议始终使用内联函数而不是宏。根据C ++的创建者Bjarne Stroustrup博士的说法,宏在C ++中几乎不需要宏,并且它们容易出错。在C ++中使用宏存在一些问题。宏无法访问类的私有成员。宏看起来像函数调用,但它们实际上不是。

  例如:

#include <iostream>
using namespace std;
class S
{
    int m;
public:
#define MAC(S::m)    // error
};

C ++编译器检查内联函数的参数类型,并正确执行必要的转换。预处理器宏无法执行此操作。另一件事是宏由预处理器管理,内联函数由C ++编译器管理。 

 

内联函数和类:
  也可以在类中定义内联函数。实际上,类中定义的所有函数都是隐式内联的。因此,这里也应用了内联函数的所有限制。如果需要在类中显式声明内联函数,则只需在类中声明函数,并使用inline关键字在类外部定义它。
例如:

class S
{
public:
    int square(int s); // declare the function
};
 
inline int S::square(int s) // use inline prefix
{
 
}

 

(2)深入理解inline函数

编程用例:

#include <stdio.h>
#include <iostream>

#define FUNC(a, b) ((a) < (b) ? (a) : (b))

inline int func(int a, int b)
{
	return a < b ? a : b;
}

int main(int argc, char *argv[])
{
	int a = 1;
	int b = 3;
	int c = FUNC(++a, b);

	printf("a = %d\n", a);
	printf("b = %d\n", b);
	printf("c = %d\n", c);

	std::cin.get();
	return 0;
}

你可能会奇怪于:为什么a = 3 而不是 a = 2,这是因为 :

#define FUNC(a, b) ((a) < (b) ? (a) : (b))

--a<b,所以输出--a;相当于int c = ((++a))<(b)?(++a):(b));

此时,我们不妨看一下它的汇编代码:

可以看到编译器编译了int c = func(++a, b)函数。 

接下来我们调用函数inline int func(int a, int b):

#include <stdio.h>
#include <iostream>

#define FUNC(a, b) ((a) < (b) ? (a) : (b))

inline int func(int a, int b)
{
	return a < b ? a : b;
}

int main(int argc, char *argv[])
{
	int a = 1;
	int b = 3;
	int c = func(++a, b);

	printf("a = %d\n", a);
	printf("b = %d\n", b);
	printf("c = %d\n", c);

	std::cin.get();
	return 0;
}

输出:

发现内联函数输出的答案与我们所想的一致。

此时,我们查看汇编代码:

可见,编译器执行了内联函数int c = func(++a, b);

在Visual Studio中还可以设置属性,让编译器强制访问inline函数:

编译运行,查看汇编代码:

(3)内联函数的另外几个特点:

1.内联函数具有普通函数的特征
2.函数的内联请求可能被编译器拒绝
3.函数被内联编译后,函数体直接扩展到调用的地方

二、内联函数进一步探索

(1)编程实验

#include <stdio.h>
#include <iostream>
//__forceinline
//__attribute__((always_inline))
inline
int add_inline(int n);

int main(int argc, char *argv[])
{
	int r = add_inline(10);

	printf(" r = %d\n", r);

	return 0;
}

inline int add_inline(int n)
{
	int ret = 0;

	for (int i = 0; i<n; i++)
	{
		ret += i;
	}

	system("pause");
	return ret;
}

查看汇编代码:

可见编译器调用了内联函数。 

当我们设置属性:

查看汇编代码:

可以看到,编译器调用了内联函数。 

三、C++中inline 内联编译的限制注意事项:

1.不能存在任何形式的循环语句
2.不能存在过多的条件判断语句
3.函数体不能过于庞大
4.不能对函数进行取址操作
5.函数内联声明必须在调用语句之前

<全文完>

参考资料:

1)https://www.geeksforgeeks.org/inline-functions-cpp/

2)https://ke.qq.com/course/204044

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值