c语言函数参数野指针,C语言陷阱与技巧第18节,函数式宏定义的“缺陷”,没有参数类型检查,产生多次副作用怎么办?...

在之前的文章里,我们曾讨论C语言程序开发中 define 宏定义的“陷阱”之一就是可能会产生多次“副作用”,这也是C语言中函数式宏定义与真正函数的主要区别之一。显然,define 宏定义的这种“陷阱”会导致程序存在隐患,而且这种隐患造成的危害不亚于“野指针”。

56d64f9667f739b915f68da02d2eb633.png

C语言函数式宏定义的缺陷

例如这面这个经典的例子,请看相关C语言代码:

#define max(a, b) ( (a)>(b)?(a):(b) )max 宏接收两个参数,并且返回较大的参数值。如果该宏在一个较大的C语言项目中较为频繁的使用,很难保证每次传递给 max 的两个参数不是计算表达式,也就是说 max 宏的参数 a 和 参数 b 有可能是一个计算表达式,例如:

int val = 3;int m = max(val++, 2);上面这两行C语言代码常常会给程序员一种 val++ 只会执行一次的错觉,但是事实上编译器会将上述代码预处理为:

int val = 3;int m = ( (val++)>(2)?(val++):(2) );也就是说,val++ 会被执行两次(即产生两次副作用),执行完这两条语句后,val 是等于 5 ,而不是等于 4 的。编写C语言代码测试之:

70704159638305c94097e1132f2fcdd3.png

编译并执行这段C语言代码,得到如下输出:

# gcc t.c# ./a.out val = 5, m = 4这样的错误虽然很简单,但是人常常会对这种“摆在眼前的错误”视而不见,所以花费大量时间才能定位到它也不足为奇。另外,这样的错误又会显得“飘忽不定”,因为如果传递给 max 的两个参数,后一个数比前一个数大,则 val++ 又会只执行一次了,例如:

int val = 3; int m = max(val++, 6);// val=4, m=6这种类型的错误在实际的C语言项目开发中,相当烦人。

事实上,我就遇到过这样的错误,而且花了一些时间才找到问题代码。

61a82b4dbabb6212ac70532d506fa022.png

避免多次副作用

C99 对 C语言做了一定的扩展,”({ … })” 就是其中之一(这个符号我们之前讨论过),可以把这个符号包裹的代码理解为一句,例如:

val = ({ a = 3; c = a+b; c;})上面这段C语言代码相当于下面这句:

a = 3;val = a+b;所以基于此,我们可以对前面提到的有“缺陷”的 max 宏做一点改进,请看:

#define maxint(a, b) ({ int _a = a, _b = b; _a>_b?_a:_b; })使用中间变量 _a 和 _b 看似麻烦,但是有两个好处:可以防止传入计算表达式时产生的多次“副作用”,而且还使 maxint 宏具备了参数类型检查的功能。

b359b7a10326a2c6ac168869e2c03d42.png

C语言是一门高效的编程语言,因此它关心数据的类型,不同类型的数据相比较有时候会产生不预期的结果。这其实也属于C语言中宏的“缺陷”,因此一般能够使用函数完成的工作都不建议再使用宏。如果某个功能的代码比较简单,希望提升其效率,可以使用 inline 函数(内联函数)定义。

总之,除非某个宏能够提供非常大的便利,否则非常不建议使用宏。

经过改进的 maxint 宏能够提供参数类型检查,这主要得益于中间变量的使用。因此如果传递给 maxint 宏一个浮点数,maxint 宏会将其截断成 int 型再做比较,例如:

val = maxint(5, 8.14);执行完毕后,val 是等于 8 的。

958f5f02198267840fc59ffa0c55075d.png

另外一个小技巧

在使用三目运算符“?:”时,可以考虑下面这个小技巧,请看:

p = x>y?:y;将 ?: 之间的数值省去,也是C99中的一个新特性,至于该技巧有哪些性质,以及可以应用于哪些场合,留给读者自己思考了。

小结

本节主要讨论了C语言中 define 宏的两个“缺陷”——可能产生多次“副作用”,以及难以提供参数的类型检查。不过也应该明白,这些缺点有时候会成为非常有用的特点,它们可以与函数互补,提供更加灵活的功能。但是如果不希望某个宏具有这两个特点,可以考虑本节提供的小技巧。

举报/反馈

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值