六、内联函数分析
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
只是一种请求,编译器不一定允许这种请求内联函数省去了函数调用时压栈、跳转和返回等开销