【C】浅析 #define 宏和函数的区别

目录

1. 执行速度(效率)

2. 调试的难易程度

3. 是否增加代码长度

4. 是否可以传入参数类型

5. 是否带来副作用

5.1 宏定义未添加 括号

5.2 宏定义引入的副作用

5.3 使用函数实现同样的功能

6. 总结

总结下几种替换宏的方式


本篇将通过以下几个方面来阐述 #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。

关于宏实现求最大值的代码可参考 用宏来实现求两个数中的最大值icon-default.png?t=N7T8https://blog.csdn.net/sustzc/article/details/100637906

因此,我们应该 尽量避免使用带有副作用的宏参数

6. 总结

综上所述,我们发现,宏和函数相比:有优势,也有劣势。

优势体现在

  • 增强代码的复用性。
  • 编译期间完成替换,提高程序性能。

劣势体现在

  • 宏不方便调试。
  • 没有类型安全的检查。

那么是否有一种方法既可以替代宏,又可以替代函数呢?

答案是肯定的,那就是内联函数(inline)。它接收数据类型的检查,并且可以像宏那样展开,还可以像函数一样被调试。

6.1 总结下几种替换宏的方式

  • C++ 中使用 const 来代替宏常量;
  • 使用 inline 来代替宏函数(包含类型检测);
  • 使用 typedef 来代替 define 定义的类型;
  • 使用 enum 来代替定义多个宏常量,并且 enum 定义的常量可读性更高。
  • 23
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值