C++ 内联函数分析
1、常量与宏
C++
中的const
常量可以替代宏常数定义:
#define A 3 const int A = 3;
我们还可以利用宏来定义宏代码片段:
#define FUNC(a, b) ((a) < (b) ? (a) : (b))
但是宏代码块不是函数, 常带有副作用,为消除副作用,用函数来替代,但是函数在调用的是由有参数的入栈,函数的返回,栈变量的销毁等等内存的开销,宏代码块则是完全没有的,为综合两者的优点,C++出现了内联函数。
宏代码块是由预处理处理,进行简单的文本替换,没有经过任何编译过程,因此可能出现副作用,而内联函数则是在编译阶段进行处理,具有函数的特征(参数检查、返回类型等)。
由于宏使用简单的文本替换,对于有些情况,在同一个作用域中同一个宏使用两次会出现重定义错误。
#define SWAP(a,b)\ int tmp = a; \ a = b; \ b = tmp; int main() { int x = 10; int y = 5; SWAP(x, y); SWAP(x, y);//此处会出错 system("pause"); return 0; }
2、内联函数
C++中推荐使用内联函数替代宏代码片段,基本形式如下:
inline int func(int a, int b) { return a < b ? a : b; }
- C++编译器可以将一个函数进行内联编译
- 被C++编译器内联编译的函数叫做内联函数
- C++编译器直接将函数体插入函数调用的地方
- 内联函数具有普通函数的特征(参数检查、返回类型等)
- 内联函数是以空间换时间的做法,没有普通函数调用时的额外开销
- 函数被内联编译后,函数体直接扩展到调用的地方
- C++编译器不一定满足函数的内联请求
inline
关键字声明可以将一个函数声明为内联函数,但是这种声明不是绝对的,inline
是对编译器的一种请求,请求编译器将对应函数进行内联编译,编译器是可以拒绝的,是否可以内联成功,要看编译器。
inline
内联函数声明时,inline
关键字必须要和函数定义结合在一起,仅将内联放在声明前是不起作用的。
#include <stdio.h> // 宏代码块,比较两个数的大小 #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); // ((++a) < (b) ? (++a) : (b)) // 2 < 3 ? 3 : 3 // 宏代码块的缺陷 int d = func(++a, b); printf("a = %d\n", a); printf("b = %d\n", b); printf("c = %d\n", c); printf("d = %d\n", d); return 0; }
查看反汇编进行分析
inline int func(int a, int b) ; func函数在这里被编译 { 00EA17B0 push ebp 00EA17B1 mov ebp,esp 00EA17B3 sub esp,0C4h 00EA17B9 push ebx 00EA17BA push esi 00EA17BB push edi 00EA17BC lea edi,[ebp-0C4h] 00EA17C2 mov ecx,31h 00EA17C7 mov eax,0CCCCCCCCh 00EA17CC rep stos dword ptr es:[edi] return a < b ? a : b; 00EA17CE mov eax,dword ptr [a] 00EA17D1 cmp eax,dword ptr [b] 00EA17D4 jge func+31h (0EA17E1h) 00EA17D6 mov ecx,dword ptr [a] 00EA17D9 mov dword ptr [ebp-0C4h],ecx 00EA17DF jmp func+3Ah (0EA17EAh) 00EA17E1 mov edx,dword ptr [b] 00EA17E4 mov dword ptr [ebp-0C4h],edx 00EA17EA mov eax,dword ptr [ebp-0C4h] } ... ... ... int d = func(++a, b); 调用函数 01001873 mov eax,dword ptr [a] 01001876 add eax,1 01001879 mov dword ptr [a],eax 0100187C mov ecx,dword ptr [b] 0100187F push ecx 01001880 mov edx,dword ptr [a] 01001883 push edx 01001884 call func (01001177h) ; 这里就是func的函数调用 01001889 add esp,8 0100188C mov dword ptr [d],eax
发现编译器并没有内联成功,还是普通的函数调用
对编译器进行设置
再看反汇编代码
inline int func(int a, int b) ; 内联函数在这里不会被编译 { return a < b ? a : b; } ... ... ... int d = func(++a, b) ; 函数调用的时候,直接将整个函数体插入调用的地方进行编译 00BC4EC3 mov eax,dword ptr [a] 00BC4EC6 add eax,1 00BC4EC9 mov dword ptr [a],eax 00BC4ECC mov ecx,dword ptr [a] 00BC4ECF cmp ecx,dword ptr [b] 00BC4ED2 jge main+7Fh (0BC4EDFh) 00BC4ED4 mov edx,dword ptr [a] 00BC4ED7 mov dword ptr [ebp-0F4h],edx 00BC4EDD jmp main+88h (0BC4EE8h) 00BC4EDF mov eax,dword ptr [b] 00BC4EE2 mov dword ptr [ebp-0F4h],eax 00BC4EE8 mov ecx,dword ptr [ebp-0F4h] 00BC4EEE mov dword ptr [d],ecx
没有了函数调用的代码,直接是将内联函数块编译进来了,省去了函数调用的开销,内联请求成功。
在Linux系统中用g++编译器编译,也是一样的情况,可以通过配置g++ 编译器达到内联效果
- 现代C++编译器能够进行编译优化,一些函数集是没有
inline
声明,也可能被内联编译 一些现代C++编译器提供了扩展语法,能够对函数进行强制内联,如:
g++ : __attribute__((always_inline)) MSVS: __forceinline
强制内联
#include <stdio.h> //__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; } return ret; }
注意C++中inline
内联编译的限制:
- 不能存在任何形式的循环语句
- 不能存在过多的条件判断语句
- 函数体不能过于庞大
- 不能对函数进行取址操作
- 函数内联声明必须在调用语句之前
3、小结
C++中可以通过inline
声明内联函数编译器直接将内联函数体扩展到函数调用的地方
inline
只是一种请求,编译器不一定允许这种请求内联函数省去了函数调用时压栈、跳转和返回等开销