内联函数在编译时进行展开,在调用点处产生一个函数体代码的拷贝。因此,对内联函数进行任何修改后,都需要对所有调用该函数的模块进行重新编译,否则它将会继续使用旧的函数。
如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。
内联函数的作用:
引入内联函数的目的是为了解决程序中函数调用的效率问题,函数的调用会有函数栈帧的开辟和函数内资源回收处理时产生的开销。如果函数功能比较简单,如整数比较大小函数int Compare_Int(int a,int b){ return a-b;}
,此时函数体很小,而调用函数的开销却很大,调用这样的函数效率不会很高。如果我们使用宏定义的方式调用宏函数,效率是有了保证,可函数安全性就没有保证了。宏展开是在预编译阶段进行的,该阶段只是做简单的替换和展开工作,没有类型检查不安全。因此,内联函数的编译时在调用点展开的特性刚好可以解决这个问题。
提问:
-
内联函数什么时候展开?
编译期展开(如果在预编译期展开不会检查类型) -
内联函数一定会展开吗?
inline是对编译器的一个建议,编译器会根据具体情况考虑是否处理。如递归函数不可以是内联函数。其中,内联函数在编译时(vs 2019):
在debug中可以调试,会产生一个local符号,也会进行函数栈帧开辟之类的动作
在realse版本中,不会产生符号和栈帧的开辟,会在调用点进行展开
内联函数、宏函数和普通函数比较:
inline函数 | static函数 | 宏函数 | |
---|---|---|---|
符号 | debug中产生local符号,realse中不产生符号 | 产生local符号 | 不产生符号 |
栈帧 | debug中进行开辟栈帧等操作,realse直接在调用点展开(编译器) | 进行栈指针操作 | 在调用点展开(预编译期) |
调试 | 可以调试 | 可以调试 | 不可以调试 |
安全 | 有类型检查 | 有类型检查 | 没有类型检查 |
代码分析:对于求和函数sum()
的分析。
#include <iostream>
using namespace std;
#define SUM(a,b) {(a)+(b)} // 宏函数
int sum(int a, int b) //普通函数 global符号
{
/* 开销(形参开辟空间)
push ebp
move ebp,esp
sub esp,0cch
……
*/
return a + b;
/* 开销(返回值的带出)
mov eax,dword ptr[a]
add eax,dword ptr[b]
*/
}
/* 开销(回收函资源)
pop ……
mov esp,ebp
pop ebp
ret
*/
inline int sum2(int a, int b) //内敛函数 不产生符号(realse) debug产生local符号
{
return a + b;
}
inline int sum3(int a, int b); //声明在编译期产生符号,存在符号表中 *UND*
static int sum4(int a, int b) //静态函数 local
{
return a + b;
}
// 实现求和运算 -> Sun(int,int)
int main()
{
int a = 10;
int b = 20;
// 方案一:调用函数实现求和
int c = sum(a, b); //反汇编--->指令很多——效率低
/*
mov eax,dword ptr[b]
push eax
mov eax,dword ptr[a]
push eax
call sum(0x……)
add esp,8
*/
// 方案二:直接求和
int d = a + b; //反汇编--->指令少——效率提升,但没有封装,不可以重复使用
/*
mov eax,dword ptr [a]
add eax,dword ptr [b]
mov dword ptr [d],eax
*/
// 方案三:宏函数 //效率提升,但不安全
int f = SUM(a, b);
/*
优点:
不存在 栈帧开辟,不存在参数的带入,不存在返回值的带出,不存在参数的清除
缺点:
不存在类型、安全检查,不可以调试
*/
// 方案四:内联函数
int e = sum2(a, b);
/*
e = a + b; //展开
*/
return 0;
}
引用:以下引用自博客 内联函数的声明和定义
inline函数的规则
-
一个函数可以自已调用自已,称为递归调用(后面讲到),含有递归调用的函数不能设置为inline;
-
使用了复杂流程控制语句:循环语句和switch语句,无法设置为inline;
-
由于inline增加体积的特性,所以建议inline函数内的代码应很短小。最好不超过5行。
-
inline仅做为一种“请求”,特定的情况下,编译器将不理会inline关键字,而强制让函数成为普通函数。出现这种情况,编译器会给出警告消息。
-
在你调用一个内联函数之前,这个函数一定要在之前有声明或已定义为inline,如果在前面声明为普通函数,而在调用代码后面才定义为一个inline函数,程序可以通过编译,但该函数没有实现inline。比如下面代码片段:最终没有实现inline;
//函数一开始没有被声明为inline:
void foo();
//然后就有代码调用它:
foo();
//在调用后才有定义函数为inline:
inline void foo()
{
......
}
- 为了调试方便,在程序处于调试阶段时,所有内联函数都不被实现。
使用内联函数时应注意以下几个问题:
- 在一个文件中定义的内联函数不能在另一个文件中使用。它们通常放在头文件中共享。
- 内联函数应该简洁,只有几个语句,如果语句较多,不适合于定义为内联函数。
- 内联函数体中,不能有循环语句、if语句或switch语句,否则,函数定义时即使有inline关键字,编译器也会把该函数作为非内联函数处理。
- 内联函数要在函数被调用之前声明。关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。