一个完美MAX宏的诞生及进化

简介: 一个完美MAX宏的诞生及进化

  我们在写代码的时候经常会需要比较两个值的大小,进而获取最大值或最小值,而最常用的方法是把其写成标准的宏,方便移植的同时也增加了代码的阅读性。

       当然,当你在面试的时候也很有可能面试官会让你写一个MAX的宏定义来侧面反映你的编程功底,虽说无法考量一个程序员的实际功底有多强,但足以反映出一个程序员的基本素质是否达标。

       到这里很多人可能都会想,MAX宏?比较两值的大小?这不是很简单嘛,简直就是信手拈来啊,于是就直接写出了下面的宏:

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

如果写成上面这样子,那么恭喜你,你离写出完美的bug又进了一步。

下面我们使用以下代码来测试下上述宏的工作情况来看一下:

#include "stdio.h"
#define MAX(a, b)   a > b ? a : b
int main(void)
{
   printf("MAX(a, b) = %d\r\n", MAX(1!=2, 1==2));
   return 0;
}

上面代码的输出如下:

MAX(a, b) =0

为什么会产生上面的结果呢?1!=2为真, 1==2为假,MAX的结果应该非0才对呀,为什么会为0?

下面来分析下这个宏,把宏展开来看:

1!=2>1==2?1!=2 : 1==2;

由于比较运算符‘>’的优先级高于‘!=’,因此运算顺序发生了改变,所以结果和我们预期的不一样了,为了避免宏展开后出现优先级的问题,可以把宏的参数加上小括号来防止展开后发生运算顺序错误的问题。

改进后的宏如下:

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

上面这个宏是MAX的第一次进化,完成第一个华丽的蜕变,但是问题来了,进化后的宏就真的没有问题了吗?

我们再使用测试代码来测试一下这个宏,看他能不能经的住考验!

#include "stdio.h"
#define MAX(a, b)   (a) > (b) ? (a) : (b)
int main(void)
{
   printf("MAX(a, b) = %d\r\n", 2 + MAX(3, 4));
   return 0;
}

从上述代码中不难看出,我们期望用2加上3和4中的最大的那个值,我们期望的输出结果是6,那就来验证下输出结果的正确性:

使用上述代码测试结果如下:

MAX(a, b) =3

????小朋友,是否有大大的问好?

我们期望的输出是6,实际输出是3,这是为什么呢?

我们继续展开来分析宏,展开后宏如下:

2+ (3) > (4) ? (3) : (4)

展开后是不是一目了然了,因为运算符‘+’的优先级也大于‘>’,因此2+3=5>4成立,因此输出3。

知道这个bug后,怎么修改呢?

接下来再看MAX宏的第二次进化,进化后如下所示:

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

我们使用小括号把整个宏定义括起来看作一个整体就可以很好的避免上述的问题。

那到这里就是一个完美宏?这就诞生了?

当然没那么简单,这个宏还是有bug。

我们再使用下面的这段代码测试一下这个宏

#include "stdio.h"
#define MAX(a, b)   (a) > (b) ? (a) : (b)
int main(void)
{
   int a = 2;
   int b = 3;
   printf("MAX(a, b) = %d\r\n", MAX(++a, ++b));
   return 0;
}

上述代码的意图也很明显,想要在a和b自加后再去比较两个值的大小,++a运行后a = 3; ++b运行后b = 4; 因此我们期望的输出值是4

来看下实际输出多少,如下:

MAX(a, b) =5

????

继续宏展开

((++a) > (++b) ? (++a) : (++b))

宏展开后可以看到

当a=2,b=3时,b自加了两次,因此MAX的输出为5。

为了解决上面的问题,MAX宏继续第三次的蜕变、进化:

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

我们再使用测试代码去测试这个宏

#include "stdio.h"
#define MAX(a,b)({                         \
                               int _a = a;       \
                               int _b = b;       \
                               _a > _b ? _a : _b; \
                        })
int main(void)
{
   float a = 2.1;
   float b = 3.2;
   printf("MAX(a, b) = %f\r\n", MAX(a, b));
   return 0;
}

在这段代码中我们期望的输出是3.2,但实际的输出如下:

MAX(a, b) =3

在宏里面我们使用的是int定义的中间变量,因此不管我们传什么参数进去,都会被隐式强制转换成整形数比较,出现上面的输出结果也就不奇怪了,那么怎么解决呢?

为了解决上面的问题就修改一下宏的定义,如下:这也是MAX宏的第四次进化

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

给宏增加一个类型参数,我们再次运行上述的测试代码就会发现可以正常的输出最大值3.2了。

在面试的时候如果能写出上面的宏,面试官应该就很满意了,知道你这个小伙子功底不错,适合来我司继续深(tuo)造(fa)

至此,应该没有问题了吧,可以拍拍胸脯说我们的代码是没有bug的,找出一个bug算我输,但是真的是这样吗?

为了完美,对得起标题,我们再使用测试代码去测试这段代码:

#include "stdio.h"
#define MAX(type,a,b)({                     \
                               type _a = a;       \
                               type _b = b;       \
                               _a > _b ? _a : _b; \
                           })
int main(void)
{
   float a = 2.1;
   int b = 3.2;
   printf("MAX(a, b) = %f\r\n", MAX(int,a, b));
   return 0;
}

当传入MAX宏的两个参数类型不同时就会出现问题,我们的宏type到底该写那个呢?上述的代码输出为:

MAX(a, b) =0.000000

发现这完全不是我们期望的输出,是个异常值,但是编译器又不会报错或警告,如果靠程序员在茫茫代码中寻找参数类型的问题,等你找到了,公司怕也要破产咯。

那么怎么避免?

这就迎来了宏的第五次进化

#define max(a, b)       ({                       \
                           typeof(a) _a = (a);   \
                           typeof(b) _b = (b);   \
                           (void) (&_a == &_b); \
                           _a > _b ? _a : _b; })

和上一次进化最大的区别就是省去了type的类型参数,使用typeof来获取传入参数的类型并定义中间变量,这样既优化了宏的参数,也优化了宏的应用场景的多元化。

这样就解决了参数不同带来的问题了吗?

答案是否定的,并没有解决,那么要这个宏,看上去这么复杂干嘛呢?

我们仔细分析下宏的第三句,是整个宏的点睛之笔。(void) (&a == &b);  表面上看上去像是在判断两个参数的地址是否相同,有些人到此就说了,用屁股想想地址也不相同呀,这不是一句废话吗?

其实编译器在判断两个参数的地址是否相同的时候会先对其类型进行判断,如果相同则继续判断地址是否相同,否则编译器会给出一个警告,当程序员误把参数类型混用的时候,这时编译器就会给出警告报警,程序员自然也就可以直接的看到这个问题并处理,总比什么都不显示,让程序员慢慢找要好的多吧!

至此就真的结束了,进化也完成了,前后一对比,MAX宏发生了翻天覆地的变化,当然这些也都是血泪史啊,在项目中积累下来的经验,在大坑中获取到的宝藏。

相关文章
|
数据采集 监控 搜索推荐
电商关键词研究:数据收集挑战与解决方案
关键词研究的重要性 深入的研究可以为卖家提供以下信息: 竞争对手数据; 内容营销的点子; 消费趋势; 客户的需求。
|
Linux
如何看懂火焰图
如何看懂火焰图
1869 0
如何看懂火焰图
|
XML 安全 IDE
【C/C++ 实用工具】CppCheck:静态代码检测工具,让你的代码更安全
【C/C++ 实用工具】CppCheck:静态代码检测工具,让你的代码更安全
3781 2
|
安全 C语言
snprintf的用法
简要介绍了snprintf的常用方法,能大大的简化我们的代码
|
传感器 数据采集 数据格式
RS485和Modbus通信协议,让工业自动化更高效!
RS485和Modbus通信协议,让工业自动化更高效!
|
开发工具 git Python
代码管理记录(二):Github代码上传实操
本文是关于如何使用Git将本地代码上传到GitHub的实操指南。介绍了Git的基本概念、安装方法,并通过详细的步骤指导用户从GitHub创建仓库到使用Git命令初始化、添加、提交代码,最终将代码推送到远程仓库。同时,还汇总了一些常见的错误及其解决方法。
470 2
代码管理记录(二):Github代码上传实操
|
缓存 负载均衡 Java
c++写高性能的任务流线程池(万字详解!)
本文介绍了一种高性能的任务流线程池设计,涵盖多种优化机制。首先介绍了Work Steal机制,通过任务偷窃提高资源利用率。接着讨论了优先级任务,使不同优先级的任务得到合理调度。然后提出了缓存机制,通过环形缓存队列提升程序负载能力。Local Thread机制则通过预先创建线程减少创建和销毁线程的开销。Lock Free机制进一步减少了锁的竞争。容量动态调整机制根据任务负载动态调整线程数量。批量处理机制提高了任务处理效率。此外,还介绍了负载均衡、避免等待、预测优化、减少复制等策略。最后,任务组的设计便于管理和复用多任务。整体设计旨在提升线程池的性能和稳定性。
374 5
|
Web App开发 存储 安全
Python编写脚本,打开浏览器输入网址,自动化登陆网站
Python编写脚本,打开浏览器输入网址,自动化登陆网站
1626 5
|
数据采集 安全 算法
李飞飞数字表兄弟破解机器人训练难题!零样本sim2real成功率高达90%
李飞飞团队提出“数字表兄弟”(Digital Cousins)概念,通过自动化创建数字表兄弟(ACDC)方法,大幅提升了机器人在真实环境中的训练效果。该方法在零样本sim2real迁移实验中成功率达到90%,显著优于传统方法。
328 3