总结
宏函数的使用不仅仅是为了炫技,而是一种高效的手段,同样C++提供的内联函数,也是非常有效的手段,这里来详细说明一下宏函数和内联函数的区别与相同点。
废话说到这里,开始直接上干货
宏函数
为什么要使用宏函数
因为很装X,emmmmm,开个小玩笑。
在C程序中,可以用宏代码提高执行效率。宏代码本身不是函数,但使用起来像函数。
在预处理阶段,预处理器就将以替换的方式将宏函数的内容进行替换,省去了参数压栈,生成汇编语言的CALL调用,返回参数,执行return等过程,从而提高了程序的效率。
但是使用宏函数有个致命的缺点就是可能出现边际效应,即宏函数,无脑的替换你的代码,导致替换的代码可能和周围的代码加载一起出现问题,比如。
#include<iostream>
using namespace std;
#define A 1+2
int main()
{
cout << A * 3 << endl;
return 0;
}
> 7
一般我们希望的是(1+2)*3,最后的结果是9,但是define则是在预编译阶段,无脑的替换而已,如果只是简单的宏函数还好,如果你要定义复杂的宏函数,那么很可能出现边际效应。
带参数的宏函数
不带参的宏,一般都是定义常量,如果需要使用宏函数,基本都是带参的。不过这里是假参数,不是真正的参数,只是替换而已。
#define offsetof2(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
这里以linux内核中的一段宏函数作为讲解。首先定义宏函数offsetof2(TYPE, MEMBER)
。
里面有两个参数,分别是TYPE,和MEMBER。
后面()中的内容则是函数体。((TYPE*)0)->MEMER,这句话表示的是将0(地址),强转成TYPE*类型,然后这个类型(一般指结构体),这个结构体调用了MEMER这个成员。
然后 (size_t)&则是将结构体中的这个成员取地址,在转换成size_t类型(在32位系统中,是无符号int,在64位系统中是无符号long)
所以这里的意思就是将地址为0的结构体,得到他的成员地址,这样成员的地址到结构体指针之间的偏移量就计算出来了。
下面这段测试代码,你讲彻底了解他的意思
#include<iostream>
using namespace std;
#define offsetof2(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
struct user_info {
int a;
int b;
};
int main()
{
cout << ((user_info*)0) << endl;
cout << &(((user_info*)0)->b) << endl;
cout << (size_t)&(((user_info*)0)->b) << endl;
cout << offsetof2(user_info, b) << endl;;
return 0;
}
输出结果
00000000
00000004
4
4
这样实现的效率很高,如果你要实现的函数体中代码很少,但是需要重复使用,那么使用宏函数,可以大大节省函数调用的时间。
不仅如此, 如果宏函数和函数名相同,那么优先使用宏定义。
其实也很好理解,因为宏函数是在预编译阶段起作用。
内联函数
这里我们在讲一下函数的调用过程。
函数调用在执行时,首先要在栈中为形参和局部变量分配存储空间,然后还要将实参的值复制给形参,还需要将函数的返回地址放入栈中,最后才跳转到函数内部执行。因此如果函数内部代码消耗时间很短的话,那么相关而言,函数调用时间就非常耗费时间。
另外,函数执行return语句返回时,需要从栈中回收形参和局部变量占用的空间,然后从栈中取出返回地址,在跳转到该地址继续执行,这个过程也是耗时的。
在C中,我们常用宏函数来解决这个问题,但是宏函数用不好容易出现边际效应,因此C++中常用inline(内联)函数来解决函数的调用开销问题。即:
inline int Max(int a, int b)
{
if(a>b)
return a;
return b;
}
内联函数在编译器处理调用内联函数的语句时,不会将该语句编译成函数调用的指令,而是直接将整个函数体的代码插入调用语句处,就像整个函数体在调用处被重写了一遍一样。
因此内联函数也可以省去调用函数的额外开销,但是代码量会增加,因为它是将函数体调用处的地方重写了,增加了代码的冗余,有点用空间换时间的感觉。
特性
- 内联函数必须是和函数体声明在一起才有效。不建议将声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
内联函数和宏函数的异同点
相同点:
- 都可以节省函数调用的开销
不同点
- 内联函数在编译时执行,而宏函数在预处理时执行。
- 宏定义不检查函数参数,返回值,只是展开;内联函数会检查参数类型
- 内联函数是真正的函数,只是在需要的时候,内联函数像宏一样展开,所以取消了函数的参数压栈。
、