内联函数

概念:

内联函数是一种用于优化代码执行效率的编程技术,其主要思想是将函数的代码插入到调用处,而不是生成函数调用的指令。这样可以避免函数调用的开销,提高程序的执行效率。

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

不加inline

int Add(int a, int b) {
	int ret = a + b;
	return ret;
}

int main() {
	int num = Add(2, 3);
	cout << num << endl;
	return 0;
}

请添加图片描述

此时是有函数调用

加上inline,在编译期间编译器会用函数体替换函数的
调用

inline int Add(int a, int b) {
	int ret = a + b;
	return ret;
}

int main() {
	int num = Add(2, 3);
	cout << num << endl;
	return 0;
}

请添加图片描述

此时没有函数的调用

如何使用:

默认在DeBug版本下,即使加上inline关键字编译器也不会对函数进行展开处理,展开内联函数会使得代码更加庞大,不利于调试,也可能会影响程序的性能和调试体验,总之,为了方便调试

在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

而在 Release 版本下,可以不用设置,在Release版本与DeBug版本下编译器可能会根据优化级别和具体情况来决定是否展开内联函数,以提高程序的执行效率

一般不建议查看Release版本下的反汇编,因为太过超前

这时只要在反汇编下查看main函数调用inline修饰函数汇编代码是否有call,没有函数调用则证明内联函数展开

适用场景:

inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用

缺陷:可能会使目标文件变大

优势:少了调用开销,提高程序运行效率


inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性

总之,一般函数规模较小的编译器会将函数展开

验证如下:

inline int Add(int a, int b) {
	int ret = a + b;
	cout << ret << endl;
	cout << ret << endl;
	cout << ret << endl;
	cout << ret << endl;
	cout << ret << endl;
	return ret;
}

//DeBug版本下默认是不展开的, 方便调试
int main() {
	int num = Add(2, 3);
	cout << num << endl;
	return 0;
}

当我们扩大函数规模时

在这里插入图片描述

此时会发现编译器会忽略inline关键字,因为上述提到inline只是对编译器的一种建议,具体是否要展开取决于编译器

内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求

一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数

很多编译器都不支持内联递归函数

一个75 行的函数也不大可能在调用点内联地展开

可执行程序变大坏处?


为什么一个项目要进行声明与定义分离呢? 主要是为了方便管控

inline不建议声明和定义分离,分离会导致链接错误,函数调用的时候要通过call函数地址来进行调用,因为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 中被引用

宏定义函数和内联函数

宏定义函数是通过预处理器的宏定义来实现的,使用 #define 关键字定义宏,并在代码中使用宏名称来替换相应的代码片段,因此不会生成相应的函数调用指令,也就没有函数调用开销

优点:

  1. 没有函数调用开销

  2. 相当比较灵活,宏定义函数可以在编译时根据需要进行文本替换,可以实现复杂的代码重用和代码片段替换

缺点:

  1. 无法进行参数检查和类型检查,宏定义函数是简单的文本替换,不是真正的函数,不会进行参数检查和类型检查
  2. 因为是文本替换,可能会降低代码可读性
  3. 不能调试:由于宏定义函数是在预处理阶段进行文本替换的,因此无法像函数一样进行单步调试和查看函数的执行过程
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main() {
	int max = MAX(2, 3);
	cout << max << endl;
	return 0;
}

在这里插入图片描述

int max = MAX(2, 3);:宏定义函数展开后变成:

int max = ((2) > (3) ? (2) : (3));

内联函数在展开后,生成的代码是真正的函数代码,可以在调试器中进行单步调试,查看函数的执行过程和变量的值

宏定义函数是在预处理阶段进行文本替换的,展开后的代码没有真正的函数体,只是简单的文本替换,无法在调试器中进行单步调试,查看函数的执行过程

内联函数虽然相较于宏定义函数可以进行调试同时较小的函数展开可以减少函数调用,但是不同编译器对应内联的建议请求做法有时候是不同的,因此可以会导致代码的移植性问题

总的来说,宏定义函数适用于简单的代码替换和代码片段重用,而内联函数适用于频繁调用的简短函数和需要进行参数检查和类型检查的情况

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐