你真的了解宏 MIN(X,Y)吗?

2023-10-30

微信搜索:编程笔记本
微信搜索:编程笔记本
微信搜索:编程笔记本

点击上方蓝字关注我,我们一起学编程
欢迎小伙伴们分享、转载、私信、赞赏

今天来研究一个看似简单、实则不那么简单的问题:**定义一个求两数较小值的宏。**下面我将以百分制的方式为各种写法打分。


一、【青铜选手】得分:1分

#define MIN(x, y) x < y ? x : y

为什么给得这么低呢?我们来看看下面的例子:

#include <stdio.h>

#define MIN(x, y) x < y ? x : y

int main()
{
    int sum = 2 + MIN(3, 4);
    printf("sum is %d.\n", sum);

    return 0;
}

理论上,sum 的值应该为 5 才对。但是上面这种写法得到的结果是什么呢?我们先运行一下,看看结果:

➜  $ gcc test.c -o test
➜  $ ./test
sum is 4.

是不是很奇怪?怎么变出个 4 出来了?对于宏的问题,一种很好的方式是检查预处理后的代码,查看宏到底展开成了什么样子。下面的代码是使用 gcc -E test.c -o test.i 得到的预处理文件:

// 与本文无关的代码已略去

int main()
{
    int sum = 2 + 3 < 4 ? 3 : 4;
    printf("sum is %d.\n", sum);

    return 0;
}

可以看到,此处的宏是直接展开的。这就导致了一个问题:展开后的宏会与上下文发生直接联系,在遇到运算符优先级等问题时,会产生各种错误。

本例中,2 + MIN(3, 4) 被展开成了 2 + 3 < 4 ? 3 : 4 。由于 + 运算符的优先级大于 < 运算符的优先级,所以这一句又被解释为 (2 + 3) < 4 ? 3 : 4 。所以,最终的运算结果为 4


二、【白银选手】得分:60 分

#define MIN(x, y) (x < y ? x : y)

上面这种写法,利用一对括号将整个宏与上下文的联系独立开来。但仍然有很大的缺点,且看下面的例子:

#include <stdio.h>

#define MIN(x, y) (x < y ? x : y)

int main()
{
    int min = MIN(2 < 3 ? 6 : 5, 4);
    printf("min is %d.\n", min);

    return 0;
}

理论上,sum 的值应该为 4 才对。我们再来看一下运行结果:

➜  $ gcc test.c -o test
➜  $ ./test            
sum is 6.

又翻车了!老样子,我们再来看一下预处理后的文件吧:

// 与本文无关的代码已略去

int main()
{
    int min = (2 < 3 ? 6 : 5 < 4 ? 2 < 3 ? 6 : 5 : 4);
    printf("min is %d.\n", min);

    return 0;
}

原来,作为第一个参数的 2 < 3 ? 6 : 5 没有做到与第二个参数之间的隔离,导致了运算的粘连。本例中,MIN(2 < 3 ? 6 : 5, 4) 被展开成了 (2 < 3 ? 6 : 5 < 4 ? 2 < 3 ? 6 : 5 : 4) ,由于条件运算符具有右结合性,所以这一句又被解释为 (2 < 3 ? 6 : (5 < 4 ? (2 < 3 ? 6 : 5) : 4)) 。所以,最终的结果是 6


三、【黄金选手】得分:90 分

#define MIN(x, y) ((x) < (y) ? (x) : (y))

这种写法使用括号将两个参数独立出来,避免第二种写法存在的问题。看样子,内部、外部均已隔离干净,应该没啥问题了?!但大家可能注意到我只给了 90 分,肯定还有美中不足之处。且看下面例子:

#include <stdio.h>

#define MIN(x, y) ((x) < (y) ? (x) : (y))

int main()
{
    double x = 1.0;
    int min = MIN(x++, 1.5);
    printf("min is %lf.\n", min);

    return 0;
}

理论上,min 的值应该为 1.0 才对。我们先来看看运行结果:

➜  $ gcc test.c -o test     
➜  $ ./test 
min is 2.000000.

又是一个出乎预料的结果。老规矩,还是看看预处理文件吧:

// 与本文无关的代码已略去

int main()
{
    double x = 1.0;
    double min = ((x++) < (1.5) ? (x++) : (1.5));
    printf("min is %lf.\n", min);

    return 0;
}

原来如此,x++ 进行了两次运算,并且没有保留自加操作前的值。所以,在这种情况下,即使 x++ 只进行一次运算,结果也是不正确的。


微信搜索:编程笔记本
微信搜索:编程笔记本
微信搜索:编程笔记本

四、【王者选手】得分:100 分

#define MIN(X, Y) (               \
    {                             \
        __typeof__(X) __x = (X);  \
        __typeof__(Y) __y = (Y);  \
        __x < __y ? __x : __y;    \
    }                             \
)

使用 __typeof__ 获取参数类型,并基于参数值定义一个新的变量,使用自定义变量的值进行大小判断。这种写法正确的原因是,不会受参数表达式的影响,且参数纸之间不会粘连。测试一下上述三种情况:

#include <stdio.h>

#define MIN(X, Y) (               \
    {                             \
        __typeof__(X) __x = (X);  \
        __typeof__(Y) __y = (Y);  \
        __x < __y ? __x : __y;    \
    }                             \
)

int main()
{
    int sum = 2 + MIN(3, 4);
    printf("sum is %d.\n", sum);

    int min1 = MIN(2 < 3 ? 6 : 5, 4);
    printf("min1 is %d.\n", min1);

    double x = 1.0;
    double min2 = MIN(x++, 1.5);
    printf("min2 is %lf.\n", min2);

    return 0;
}

运行结果:

➜  $ gcc test.c -o test
➜  $ ./test            
sum is 5.
min1 is 4.
min2 is 1.000000.

这就是大名鼎鼎的 GNU 中的写法。


其实,我们是局限在宏定义的范围内来实现取小函数。之所以会出现各种各样的问题,是由于宏的本质是文本的直接替换,这会引入表达式之间、参数之间的粘连,且表达式的值与运算次数都无法保证正确。

其实,如果使用函数实现,所有的问题都会迎刃而解,因为函数会提供天然的隔离环境与求值操作:

int min(int x, int y)
{
    return x < y ? x : y;
}

但是使用函数实现有一个不足之处,函数只能针对特定类型的数据进行取小操作,这也是宏定义的方式存在的原因。

微信搜索:编程笔记本
微信搜索:编程笔记本
微信搜索:编程笔记本

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

你真的了解宏 MIN(X,Y)吗? 的相关文章

随机推荐

  • 判断一个对象是否有某一个属性

    如果我们要检测xiaoming是否拥有某一属性 可以用in操作符 var xiaoming name 小明 birth 1990 school No 1 Middle School height 1 70 weight 65 score n
  • 关于poc的查找

    1 已知漏洞cve号 1 github https github com 2 twitter https twitter com home 3 直接浏览器搜索 google 百度 4 微信搜索 2 批量找poc 1 发现者3号有github
  • 爬虫:从入门到入狱,进去一起做兄弟

    从入门到入狱 中国爬虫违法违规案例汇总 1 一 什么是爬虫 二 爬虫的分类 搜索引擎 百度 谷歌 数据采集 天眼查 企查查 薅羊毛 抢票机器人 秒杀软件 比价软件 微博僵尸粉 三 爬虫与反爬虫 1 君子协议 robots txt www b
  • Chrome浏览器禁用更新

    操作步骤 我的电脑 进入目录 C Windows System32 drivers etc 修改hosts文件 在末尾添加 127 0 0 1 update googleapis com 保存并退出 按win r 快捷键 输入cmd打开命令
  • spring-mvc的重定向和转发

    重定向和转发 servlet的方法 Controller public class ResultGo 在页面上打印 RequestMapping result t1 public void test1 HttpServletRequest
  • 终于弄懂tf.reduce_sum()函数和tf.reduce_mean()函数

    参考博客 1 https www zhihu com question 51325408 answer 125426642 2 https www w3cschool cn tensorflow python tensorflow pyth
  • typescript 提示 Object is possibly null

    Object is possibly null 对象可能是null 分析 localStorage getItem SET HISTORY KEY 这个值有可能为空 所以再执行getItem就会报错此刻对象可能为空 解决 联合类型 把nul
  • linux单进程最大内存,限制单个Linux进程的内存使用量

    我正在运行pdftoppm将用户提供的PDF转换为300DPI图像 这非常有用 除非用户提供的页面大小非常大 pdftoppm将分配足够的内存来在内存中保存该大小的300DPI图像 对于100英寸的方形页面 每像素100 300 100 3
  • 关于Postman无法显示中文的解决方案(翻译)

    在使用Postman时很多人因为界面是纯英文的感到很头疼 会面临不知道什么意思及界面看不懂的情况 于是出现了需要将界面汉化翻译过来的需求 但从实际工作经验来讲 个人还是比较喜欢看英文界面的 可能也是看习惯了导致的吧 本文以两种方式帮助读者理
  • FPGA虚拟时钟约束详解

    FPGA虚拟时钟约束详解 在FPGA设计中 时钟是一个至关重要的因素 为了确保时序分析的准确性 并满足特定应用对时钟精度的要求 我们需要通过时钟约束来对FPGA设计进行优化和配置 本文将详细介绍FPGA虚拟时钟约束的原理与实现方法 一 什么
  • 2023,软件测试人的未来在哪里?

    2023年 IT行业出现空前的萧条 首先是年初一开始各大厂像着了魔似的不约而同的纷纷裁员 降薪 奖金包缩水 随之而来的是需求萎缩 HC减少或封锁等等 而有幸未被列入裁员名单的在职人员 庆幸之余也心有余悸 伴随着恐慌 说不定哪天裁员就轮到了自
  • Cocos2d C++与lua互相调用

    参考文章 cocos2dx之Lua调用C 与 cocos2dx之C 调用Lua 感谢 乐逍遥Jun的参考 我是用的是 3 13版本 创建一个 lua版本的工程 我的工程名称是 TestLua 一 lua 调用c 1 编写一个 ini文件 路
  • 自驱力超强的羊驼?斯坦福微调LLaMa

    大型 指令调优 语言模型在新任务上展现了Zero shot的卓越能力 但严重依赖于人类编写的指令数据 而这些数据在数量 多样性和创造性方面都是有限的 斯坦福科研人员引入了self instruction框架 提高指令遵循能力来自我迭代进化
  • 利用MATLAB做一维CNN分类 问题及解决方法

    利用MATLAB做一维数据的CNN分类 问题及解决方法 我在做一维CNN分类时参考了知乎凉拌西红柿答主的程序示例 根据其代码改编实现了CNN分类 其中遇到的问题与解决方法总结如下 1 数据维度转换问题 for i 1 1 3000 for
  • python字符识别_crnn(基于pytorch、python3) 实现不定长中文字符识别

    在六七月份参加了一个比赛 做的项目是提取图片中的文字信息 首先是接触了一些文本检测算法 如CTPN East 后研究了文本识别算法 我认为较好的是CRNN 代码实现是参考算法提出者的pytorch python3版本的crnn实现 因为py
  • Oracle 删除命令详解

    Oracle 删除表方式分为三种 第一种 drop 命令 drop table 表名 实战 drop table erms biz accep management drop table ERMS BIZ ARCH COMPANY SPEC
  • @WebServlet注解(Servlet注解)

    WebServlet注解 文章目录 WebServlet注解 前言 一 WebServlet 注解的属性 二 WebServlet 注解的使用 1 启用注解支持 2 使用 WebServlet 注解 WebServlet 注解 和 web
  • 香港政府活用无人机,正式应用到调研检测领域

    香港机电工程署正式用无人机来完成燃气管道的监测工作 完整的数据库将能帮助工程师识别高危煤气管道 近些年来 无人机的使用已经日趋广泛 逐步被应用到各个领域 近日 香港机电工程署目前正在利用无人机来检测燃气管道 香港电机工程署所采用的无人机有摄
  • python日志记录

    日志记录 日志 排错 程序调试 用户行为分析 python logging模块 日志等级 日志等级 数值表示 描述 DEBUG 10 最详细的日志 常用于调试 INFO 20 详细日志 记录关键节点 WARNING 30 当前有不期望的事情
  • 你真的了解宏 MIN(X,Y)吗?

    微信搜索 编程笔记本 微信搜索 编程笔记本 微信搜索 编程笔记本 点击上方蓝字关注我 我们一起学编程 欢迎小伙伴们分享 转载 私信 赞赏 今天来研究一个看似简单 实则不那么简单的问题 定义一个求两数较小值的宏 下面我将以百分制的方式为各种写