目录
本篇将通过以下几个方面来阐述 #define 宏 和 函数 的不同之处:
1. 执行速度(效率)
- #define 宏 的执行速度快
- #define 宏 在程序运行过程中,只执行逻辑部分,完成替换即可
- 函数的执行速度慢
- 函数存在 函数调用以及函数返回时的额外开销(即 函数栈帧)
- 函数需要做一些准备工作,比如调用函数前要先在栈中预开辟一片空间
- 调用函数时执行逻辑部分,并且会保存调用函数的地址,函数调用结束后根据保存的地址返回到之前函数调用的地方
- 函数存在 函数调用以及函数返回时的额外开销(即 函数栈帧)
2. 调试的难易程度
- #define 宏 不便于调试
- 编译报错时,往往编译器报错的地方并不是真正发生错误的地方
- #define 宏 是在编译时的预处理阶段就被编译器处理了,只完成一些替换操作
- 函数调试是比较方便的
- 函数是在程序运行过程中被处理的
3. 是否增加代码长度
- #define 宏 可能会使得代码长度 变长
- 假如宏定义特别多,那么会使得代码长度大幅度增加
- 函数并不会增加代码长度
- 函数只有在调用时才会使用定义函数的代码
- 函数调用结束后会释放掉调用期间开辟的空间,这并不会影响代码的长度
4. 是否可以传入参数类型
- #define 宏 可以传入参数类型
- 函数传参是不可以传入数据类型的
#include <stdlib.h>
// 宏在传参时是可以传入类型的
#define MALLOC(NUM, TYPE) (TYPE *)malloc(NUM * sizeof(TYPE))
void test()
{
// error, 函数在传参时是不可以传入数据类型的
int maxNum = Max(1, int, 2, int);
}
5. 是否带来副作用
- #define 宏 参数的求值需要给参数加上相应的括号
- 否则邻近操作符可能会影响所求值的结果,这是因为宏只是做了简单的替换操作
- 即使加上括号,带有副作用的宏参数 可能也会使得计算结果出错
- 函数的参数只在函数调用时求值一次,并将结果传递给函数即可
5.1 宏定义未添加 括号
#include <stdio.h>
#define SQUARE(X) X * X
int main(void) {
int ret = 0;
ret = SQUARE(3 + 1);
printf("square = %d\n", ret);
return 0;
}
我们期待的结果是 4 * 4 = 16,而运行上述程序的结果却是 7,这是因为宏只是做了简单的替换,因此可以得到替换后的结果为:3 + 1 * 3 + 1 = 7。
假如想要得到结果 16,我们可以通过加上括号的方式来解决:
#include <stdio.h>
#define SQUARE(X) ((X) * (X))
int main(void) {
int ret = 0;
ret = SQUARE(3 + 1);
printf("square = %d\n", ret);
return 0;
}
函数在调用时先将参数计算一次,然后再将计算后的结果传给函数。
#include <stdio.h>
int Square(int x) {
return x * x;
}
int main(void) {
int ret = 0;
ret = Square(3 + 1);
printf("square = %d\n", ret);
return 0;
}
5.2 宏定义引入的副作用
这里的副作用指的是 会改变内存中数据的值(如 后置 ++)。
来看下这段代码:
#include <stdio.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main(void) {
int x = 2;
int y = 5;
int ret = MAX(x++, y++);
printf("x = %d\ty = %d\tret = %d\n", x, y, ret);
return 0;
}
输出结果为:
以上就是宏参数对宏产生的副作用,首先展开宏参数可以得到 (x++) > (y++) ? (x++) : (y++),由于是后置 ++,因此先给 x,y 赋值,此时 x = 2,y = 5,显然 2 < 5,将执行 : 后面的语句,此时 x 自增 1,变成 3,y 自增 1 变成 6,而 ? 后面的语句不被执行,x 最终的值就是 3。在执行 : 后面的语句时,y 为 6,同样的后置 ++,把 6 赋给该语句,此时 ret 的值就是 6,最后 y 自增 1 得到 7。
5.3 使用函数实现同样的功能
再来看看下面这段代码:
#include <stdio.h>
int Max(int x, int y) {
return x > y ? x : y;
}
int main(void) {
int x = 2;
int y = 5;
int ret = Max(x++, y++);
printf("x = %d\ty = %d\tret = %d\n", x, y, ret);
return 0;
}
输出结果为:
调用函数 Max 时,将参数 x++,y++ 传进去,由于后置 ++,因此先赋值后自增,传给函数的值是 x = 2,y = 5,经过比较后返回 5 给函数,函数调用结束后 x,y 分别自增 1,x 变为 3,y 变为 6。
关于宏实现求最大值的代码可参考 用宏来实现求两个数中的最大值https://blog.youkuaiyun.com/sustzc/article/details/100637906
因此,我们应该 尽量避免使用带有副作用的宏参数。
6. 总结
综上所述,我们发现,宏和函数相比:有优势,也有劣势。
优势体现在:
- 增强代码的复用性。
- 编译期间完成替换,提高程序性能。
劣势体现在:
- 宏不方便调试。
- 没有类型安全的检查。
那么是否有一种方法既可以替代宏,又可以替代函数呢?
答案是肯定的,那就是内联函数(inline)。它接收数据类型的检查,并且可以像宏那样展开,还可以像函数一样被调试。
6.1 总结下几种替换宏的方式
- C++ 中使用 const 来代替宏常量;
- 使用 inline 来代替宏函数(包含类型检测);
- 使用 typedef 来代替 define 定义的类型;
- 使用 enum 来代替定义多个宏常量,并且 enum 定义的常量可读性更高。