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