C语言宏定义的那些坑

在C语言中,宏具有不可替代的地位,宏分为两种:一种是不带参数的,另一种是带参数的宏。

不带参数的宏很简单,例如

#define PI   3.14 

这样,在以后的程序代码中,凡是用到圆周率的地方都可以用PI来表示,这样写有两个好处,一是程序代码使用英文单词代替数字,使得程序更具有可读性;二是修改起来比较方便。

下面我们用一道十分常见的例题来讲讲C语言宏定义的那些坑。出代码

#include <stdio.h>
#define MAX(a,b)    a>b ? a:b

int main()
{
    int a=100,b=200;
    int m = MAX(a,b);
    printf("a=%d\n",a);
    printf("b=%d\n",b);
    printf("m=%d\n",m);
    return 0;
}

站在使用者的角度看,宏的使用除了大写字母外,和函数的调用感觉是一样的。而实际上,编译该文件时MAX()将会被其定义语句所替代,我们可以使用 -E 选项来查看宏替换之后程序的真实面孔。

gcc macro.c -o macro.i -E

之后我们会看见在程序中的MAX(a,b)被替换成了 a>b ? a:b;

世界上没有免费的午餐,这种直截了当的宏替换是把双刃剑,它在节省了函数切换时间的同时,也隐藏了很多逻辑陷阱,比如我们改一下代码,变成:

int m = MAX(a|-1,b);

执行该程序你会发现有以下输出结果:

a=100
b=200
m=-1

这时候的结构明显和我们预料的不一样。本来任何数和-1进行位或运算都将得到-1(因为-1的二进制补码全是1,即0xFFFFFFFF,任何数位或这个值都将变成全1),这样一来b当然要比a|-1要大了,但结果却不是这样,这是因为宏替换之后的代码是这样的:

int m = a|-1>b ? a|-1:b;

可以看到,之所以出现逻辑错误是因为宏替换出现了运算符优先级的问题。为了解决这个问题,我们把宏里面的每个参数都用括号括起来。于是代码变成了这样。

#define MAX(a,b)    ((a)>(b) ? (a):(b))

好了这样我们就完美解决了上面的问题了。你以为这样宏定义就没有坑了吗,那你就太天真了,如果我们再把代码改成下面那样,问题又来了。

int m = MAX(a,b++);

执行代码会有以下输出:

a=100
b=202
m=201

从程序执行结果来看,变量b的值发生了奇怪的变化,本来应该自增1的,答案却成了202.这是宏参数直接替换的结果,我们来看看预处理之后的代码就马上知晓原因了。

int m = ((a)>(b++) ? (a):(b++));

注意代码中宏替换之后,b++出现了两次,也就是说如果表达式的第一个语句结果为假时,b++将会被执行两次。这明显不符合我们的预期要求。要解决这个问题,就必须保证宏参数在宏定义中只出现一次,请看代码

#define MAX(a,b)    \
    ({\
        int _a = a;\
        int -b = b;\
        ((_a)>(_b) ? (_a):(_b));\
        })

执行程序发现输出结果没有问题。在这个版本的宏定义中改进了不少内容

  1. 在宏的内部定义了两个变量来替换a,b,防止a,b出现多次。
  2. 由于宏定义出现了多个语句,因此必须用{......}将他们形成一个复合语句。
  3. 根据C语言的语法,复合语句不能出现在表达式中,而我们的宏调用不能有这个限制,因此在花括号的外边加了一对圆括号。
  4. 每一条语句的后面都必须有一个反斜杠来结束,哪怕是空行。

最后运行程序,果然打印出了正确答案。

好了,C语言的宏定义相关的坑就讲到这里了。这个例题也是面试笔试的常见题目,希望可以帮助到大家!

思考以及程序可以优化的问题:

  1. 如何使得宏能同时处理整型和浮点型。
  2. 两个不同类型但兼容的数据给MAX(),比如一个3.14和一个100,它应该如何得出正确结果。

 大家可以思考以下这两个问题,相关的解决思路会在下一篇。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值