专栏首页循迹漫聊虚幻引擎C++中错用宏定义造成的二义性

C++中错用宏定义造成的二义性

在写代码的时候经常会用到宏(#define)命令,最近我遇到了这两个错用宏的地方,写出来分析一下。

忘记了宏的参数只是进行简单的替换

代码如下

#include <iostream>
using namespace std;
//其实宏这么写是不对的,后面说
#define MAX(a,b) a>=b?a:b
int main(void){
    int a,b;
    cin>>a>>b;
    cout<<MAX(a,b)<<endl;
    return 0;
}

上面的代码实现的是一个简单的判断两数中大值的程序,现在这里没有问题。 但是,当MAX的参数带有副作用的时候就会出现问题了:

cout<<MAX(a--,b)<<endl;

当MAX的参数为MAX(a–,b)时,如果读入的数据为a>b,那么就出现错误了:

运行之后,虽然得出MAX值为11,但是,执行完毕cout<<MAX(a--,b)<<endl;之后,a的值就变成了10

因为define只是简单的宏替换,所以,当MAX的参数具有副作用的时候就会变成这样:

#define MAX(a,b) a>=b?a:b
//假如参数a具有副作用,那么cout<<MAX(a--,b)<<endl;就等价为:
if(a-->b){
	cout<<a--<<endl
}else{
	cout<<b<<endl;
}

意味着如果读入的数据是a>b,那么a--将执行两次。

将MAX宏改写成等价的if语句后,运行结果如下:

与执行MAX宏的结果一样。

宏没有加括号造成的二义性

前面代码里写到#define MAX(a,b) a>=b?a:b这么写是不对的,原因如下。 在这里如果调用的语句中还有其他的表达式,也会造成二义性!考虑下面的代码:

#include <iostream>
 using namespace std;
#define MAX(a,b) a>b?a:b
int main(void){
    int a,b;
    cin>>a>>b;
    int max=MAX(a,b)*2;
    cout<<max<<endl;
    return 0;
}

此处int max=MAX(a,b)*2;我们预期的是将a和b中的大值乘以2再赋值给变量max。 但是正如前面所说的宏只是简单的替换,所以以上代码的实际含义是:

#define MAX(a,b) a>=b?a:b
//那么int max=MAX(a,b)*2;就等价为:
int max=a>=b?a:b*2;

运行结果如下:

当a>=b时,*2不会被执行,因为编译器把b*2当做了三目运算符的一个表达式。

所以,宏命令最安全也是最正确的写法是:

  1. 宏命令一定要加括号,保证宏命令作为独立的表达式。
  2. 宏命令的参数也一定要加括号,保证每个参数的正确使用(保证副作用不会影响其他的参数)。
#define MAX(a,b) ((a)>=(b)?(a):(b))

注意:千万要记得宏只是简单粗暴的替换,有可能会产生二义性!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • C中的预编译宏定义

    文章来自 http://www.uml.org.cn/c++/200902104.asp 在将一个C源程序转换为可执行程序的过程中, 编译预处理是最初的步骤. ...

    杨奉武
  • C++工程中常用的宏定义(#define)

    尽管说define有很多不足之处,很多时候我们需要使用const来替代define, 也可以使用typedef来替代define。 但是,在一些实际工程中,我们...

    程序员的酒和故事
  • C++工程中常用的宏定义(#define)

    尽管说define有很多不足之处,很多时候我们需要使用const来替代define, 也可以使用typedef来替代define。

    用户7886150
  • C语言中宏的定义与使用

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    耕耘实录
  • c语言之宏定义中的##和#

    西西嘛呦
  • 简述C语言宏定义的使用

    在工程规模较小,不是很复杂,与硬件结合紧密,要求移植性的时候,可采用宏定义简化编程,增强程序可读性。

    C语言与CPP编程
  • [PHP] PHP源码常用代码中的宏定义

    PHP源码常用代码宏定义: #define 宏名 字符串 #表示这是一条预处理命令,所有的预处理命令都以#开头。define是预处理命令。宏名是标识符的一种...

    陶士涵
  • DSP中的C语言(二)——结构体的定义

    简单学习一下结构体,因为在DSP里面结构体都是官方定义好的,我们用就可以,但是还是知其然也要知其所以然。

    好派笔记
  • 【编程基础第十讲】C语言常用宏定义的用法

    存在问题: 我们经常看到C语言中各种宏开关,他们是干啥的呢? 解决方案: C语言中的宏定义是最常用的组成部分之一,他们在编程时有重要作用,正确应用可以减少很多代...

    程序员互动联盟
  • 在Ocelot中使用自定义的中间件(二)

    在上文中《在Ocelot中使用自定义的中间件(一)》,我介绍了如何在Ocelot中使用自定义的中间件来修改下游服务的response body。今天,我们再扩展...

    李明成
  • Python中的用户定义异常与NZEC错误

    当代码出错时,Python会引发错误和异常,这可能导致程序突然停止。Python还通过try-except提供了异常处理方法。一些最常见的标准异常包括Index...

    用户7466307
  • Python中的用户定义异常与NZEC错误

    当代码出错时,Python会引发错误和异常,这可能导致程序突然停止。Python还通过try-except提供了异常处理方法。一些最常见的标准异常包括Index...

    PHP开发工程师
  • 深入理解C++11(一)

    导语 从最初的代号C++0x到最终的名称C++11,C++的第二个真正意义上的标准姗姗来迟。 C++11是一种新语言的开端。虽然设计C++11的目...

    MelonTeam
  • data自定义属性在jQuery中的用法

    (1)如果在HTML文档中设置的data-自定义属性的单个字符串的名称的属性中若有大写值,在js文件中获取时只能用小写的形式获取。如:

    kirin
  • C++核心准则ES.33:如果必须使用宏定义,命名要有唯一性

    Avoid macros if you can: ES.30, ES.31, and ES.32. However, there are billions of...

    面向对象思考
  • C++特性使用建议

    使用引用替代指针且所有不变的引用参数必须加上const。在C 语言中,如果函数需要修改变量的值,参数必须为指针,如int foo(int *pval),在 C+...

    Dabelv
  • Vue 给mapState中定义的属性赋值报错的解决方案

    如上,我们希望在执行increaseCount函数时,给mapstate函数中映射定义的this.count赋值,给该值增加1,结果,提示

    授客
  • C/C++中inline用法详解

    (一)inline函数(摘自C++ Primer的第三版) 在函数声明或定义中函数返回类型前加上关键字inline即把min()指定为内联。       in...

    Angel_Kitty
  • SWIG 官方文档第二部分 - 机翻中文人肉修正

    本章简要概述了 C++11 标准的 SWIG 实现。SWIG 的这一部分仍在进行中。

    韩伟

扫码关注云+社区

领取腾讯云代金券