【回头看之C++】inline内联函数
特别标注:本文部分内容源于网络,原作链接已标注于“参考阅读”部分,该作品使用 CC BY-SA 4.0 许可发布。本文亦遵循 CC BY-SA 4.0 许可。
回头看系列文章序
自大三起我认识到,随着应用知识的扩展,基础知识的重要性愈加明显。时至今日,已经到了无法忽视的地步,以至于我们必须采取有效措施,认真巩固语言基础、数据库、操作系统等一系列重要基础知识,将零散的知识点体系化,我将这一计划称之为“回头看”。
在C++编程中,inline
关键字常被用来提示编译器将函数体直接插入到调用处,通过牺牲代码空间的方法,避免了函数调用的开销,赢得了运行时间。
什么是inline
函数?
inline
函数是一种建议编译器在调用该函数时,将函数体直接替换到调用位置,而不是进行正常的函数调用。这种优化旨在减少函数调用的开销,特别是对于那些频繁调用的小函数。
声明与定义
要声明一个inline
函数,只需在函数定义前加上inline
关键字:
inline int add(int a, int b) {
return a + b;
}
也可以将其与类成员函数结合使用:
class Math {
public:
inline int multiply(int a, int b) {
return a * b;
}
};
工作机制
当编译器遇到inline
函数时,会尝试将该函数的代码展开到调用点,类似于宏替换,但不同于宏的是,inline
函数在进行替换时会进行类型检查和语法检查。
例如:
inline int square(int x) {
return x * x;
}
int main() {
int result = square(5);
return 0;
}
编译器会将main
函数中的square(5)
替换为:
int main() {
int result = 5 * 5;
return 0;
}
inline
的局限性和注意事项
- 使用限制:
inline
只适合简单的函数使用,不能包含复杂的控制语句,且不能用于递归函数,因为递归调用本质上不能展开为内联代码。 - 编译器只是建议:
inline
只是对编译器的建议,编译器不一定会按内联函数进行展开,尤其是当函数体过大或复杂时。 - 代码膨胀:大量使用
inline
函数可能导致生成的代码体积增大,因为每次调用都插入了完整的函数体。 - 调试困难:内联函数的展开可能会使调试过程变得复杂,因为实际执行的代码和源代码可能不完全一致。
隐式内联
现代编译器会智能的一些函数进行内联,即使不显式使用inline
关键字,编译器也可能会自动将一些小函数内联展开。
- 类的成员函数定义在类的声明中时,不需要
inline
关键字。
例如在结构体中的情形:
#include <iostream>
struct User
{
char name[256];
int age;
void who (void){
std::cout << "我是" << name <<",今年" << age << "岁。" << std::endl;
}
};
int main(int argc, const char * argv[]){
User user = {"张飞", 25};
user.who();
return 0;
}
函数who被声明和定义在结构体User中。该函数会被自动优化为内联函数,成为隐式内联。函数的函数体被编译形成的二进制码将直接替代上述代码中的user.who()
,也即:
void who (void){
std::cout << "我是" << name <<",今年" << age << "岁。" << std::endl;
}
宏定义与普通函数
代替普通函数:提高程序运行效率。
普通函数频繁调用的问题
函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。
栈空间是用于存放程序的局部变量和函数内部数据的内存区域。在计算机系统中,栈空间是有限的。如果频繁且大量地使用栈空间,可能会导致栈空间不足,从而引发程序错误。特别是在函数递归调用时,如果递归深度过大,最终会耗尽栈内存,导致程序崩溃。
当一个函数的代码较多且执行时间较长时,函数调用所花费的时间可以忽略不计。然而,如果一个函数只有一两条简单的语句,那么函数调用本身的开销(如参数传递、栈帧创建和销毁等)就变得相对显著,影响程序的执行效率。
内联函数解决的问题
- 减少函数调用开销:函数调用需要保存和恢复寄存器、传递参数等,
inline
函数可以避免这些开销。 - 代码优化:对于一些简单的、频繁调用的小函数,
inline
可以让编译器进行更好的优化。
宏定义与内联
内联函数的最初目的:代替部分#define宏定义。
- 宏定义:预处理指令,在预处理时对所有宏进行替换;
- 内联函数:函数,在编译阶段把有调用内联函数的地方进行插入;
为什么要用内联函数代替宏定义?
- 与宏相比,
inline
函数提供了更安全和可读的代码,因为它们受C++的类型和语法检查约束; - 宏的编写有很多限制,例如只能写一行,不能使用return控制流程等;
- 无法操作类的私有数据成员。
总结
inline
关键字是C++中用于优化函数调用的一个有用工具,但应谨慎使用。对于频繁调用的小型函数,inline
可以提高性能;但对于大型或复杂的函数,内联可能会导致代码膨胀和维护困难。了解编译器的行为和优化策略,可以帮助开发者更好地利用inline
来提升程序性能。