1、<定义一个宏,求两个数的最大值>
这个问题在嵌入式笔试题中出场率相当的高,能写出符合要求的宏很简单,但要写出完美的宏却还是有些难度的。
符合要求的宏如下:
#define MAX(a,b) a>b?a:b
多数C语言初学者顺手就能写出这个宏(PS:倘若你暂时连这个都没想到的话还是过段时间再换工作吧。),一个三目运算符直接返回最大值。这个宏在一般情况下也没什么问题,完全可以使用。
在linux环境下验证(我安装的Ubuntu):
在终端输入:mkdir C
创建一个文件夹,用来存储我学习过程中创建的文件。
cd C
打开刚刚创建的C文件夹
touch ex1.c
创建一个空文件(直接使用vi
可以省略这一步)
vi ex1.c
打开ex1.c
文件
我输入以下代码:
#include <stdio.h>
#define MAX(a,b) a>b?a:b
int main()
{
int a=3,b=5;
printf("a=%d , b=%d ",a,b);
printf("The Max Num is:%d",MAX(a,b));
}
输入:wq
保存退回终端窗口。
gcc ex1.c -o ex1.out
在终端窗口输入编译ex1.c
,并生成可执行文件的命令。
./ex1.out
输入命令执行我刚才编写的代码,执行结果如下图所示(相关文件请在本人GitHub中查看、下载):
从执行结果可以看出来我刚才编写的这个宏定义
第一次变形
但是总有特殊的条件存在嘛,像这种特殊
条件的存在,如果你在编程的时候没有考虑进去的话,这就是一个BUG。
按照王老师的教程我修改printf如下:
printf("The Max Num is:%d\r\n",MAX(a!=3,b!=3));
自己分析下预期的输出结果应该是:1
看下在Linux环境下的真是处理结果:
分析一下代码,上面的MAX展开之后应该是这样:
MAX(a!=3>b!=3?a!=3:b!=3)
贴上来一张C/C++运算符优先级的表来供参考:
运算符的优先级直接打乱了预期的执行过程,工作初期经常因为优先级的问题而写了很多的BUG。后面学聪明了,不管优先级怎么样,直接按照自己的预期流程添加小括号()
。那么按照这个思路是修改调用代码还是宏定义?正常的编程思想是需要修改宏定义的,一劳永逸。那么我修改宏定义如下:
#define MAX(a,b) (a)>(b)?(a):(b)
再次gcc ex1.c -o ex1.out
编译ex1.c
文件。
./ex1.out
运行刚编译完成的文件。
在Linux环境下的执行结果如下图所示:
从运行结果可以清楚地看到我得到了预期想要的结果,第一次变形成功。
第二次变形
再次根据王老师的教程修改代码:
printf("The Result is %d",a+MAX(a,b));
预期结果是输出 8 ,那看下Linux环境下真正的输出结果:
为什么会有这个结果,其实分析一下代码就真相大白了:
a+a>b?a:b
->3+3>5?3:5
那有了第一次的变形经验,很快就想到了再加一个小括号,也就是如果展开的表达式是这样子的就不会有问题了:3+(3>5?3:5)
。
于是我修改宏定义如下:
#define MAX(a,b) ((a)>(b)?(a):(b))
再次运行瞅一眼运行结果:
确认没问题,结果是 8 。
第三次变形
再次修改代码如下:
printf("The Result is %d",a+MAX(a++,b++));
这个预期的输出结果是 9 ,运算之后 a=4 , b=6 。
实际运行结果如下:
带入宏定义展开之后是:a+((a++)>(b++)?(a++):(b++))
通过展开的式子可以很明显的看到计算之后 a=4 b=7 ,因为 b 进行了两次自加运算。有可能会有人就是想要这种结果,但是这么隐晦、不受控的式子真的不建议去使用。
既然这样,那这个宏定义该怎么修改才能避免在调用MAX的时候参数自加两次呢?我可以在刚进入宏的时候就把参数的值取出来赋给另外的新变量,修改如下:
#define MAX(a,b) ({\
int _x=(a);\
int _y=(b);\
_x>_y?_x:_y;})
看下运行结果:
这个 9 的结果是 4+5=9 得来的,4是MAX运算之后的a的值;MAX(a,b)
就是要返回两个参数里面最大的那个,对于我的式子来说就是要返回 a++ 和 b++ 里面的最大值,也就是 b++ 。所以结果是 4+5=9。
如果对4
有疑问的话可以参考。。。。(补)
</stdio.h>
第四次变形
上面第三次变形虽然完美解决了混乱的问题,但是它又带来了另外一个不容忽视的BUG。我定义了MAX宏里面的 _x 和 _y 是 int 型的,那如果需要比较的参数是其他不定类型呢?比如MAX(2.1,1.6)
,这个时候总不能每一个不同的类型就单独定义一个MAX宏吧。将参数的类型当作一个参数传进来可能是此时此刻我能想到的最好的办法了。于是我修改MAX宏如下:
#define MAX(type,a,b) ({\
type _x=(a);\
type _y=(b);\
_x>_y?_x:_y;})
这样的话我的式子可以写成MAX(float,2.1,1.6);
Linux环境下的运行结果如下:
OK,这个宏已经是非常完美了。但是在Linux环境GCC编译器下还能够更加完美,请看第五次变形。
第五次变形
ANSI C 定义了 sizeof 关键字,用来获取一个变量或数据类型在内存中所占的存储字节数。GNU C 扩展了一个关键字 typeof,用来获取一个变量或表达式的类型。这里使用关键字可能不太合适,因为毕竟 typeof 还没有被写入 C 标准,是 GCC 扩展的一个关键字。
通过使用 typeof,我们可以获取一个变量或表达式的类型。所以 typeof 的参数有两种形式:表达式或类型。
OK,得到了上面的这个概念之后是不是突然间觉得第四次变形的宏定义还可以再次升级呢?当然可以,我来验证一下,最后一次变形了,直接贴出来ex1.c的整个截图,文件附在本篇结尾。
少不了的运行结果:
如果自己能把这个宏写到这种程度就已经很厉害了,但是LinuxKernel贡献者考虑的更多,贴上来求最大最小值在LinuxKernel中的写法:
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
#define max(x, y) ({ \
typeof(x) _max1 = (x); \
typeof(y) _max2 = (y); \
(void) (&_max1 == &_max2); \
_max1 > _max2 ? _max1 : _max2; })
仔细看上面的宏定义,比我写的多了一行:
(void) (&_max1 == &_max2); \
根据王利涛老师的讲解,我理解如下:
(&_max1 == &_max2)这个比较本身来说是没有任何意义的,并且整个宏也没有使用它的比较结果。没有使用比较结果是不是就没有用呢?再看下&_max1是变量_max1的地址,那两个变量的地址肯定是不相同的啊。然而,两个地址做对比的时候会涉及到各自的指针类型对比,当两个指针类型不相同的地址做对比时编译器会发出警告,提示指针类型不同,我自己测试结果如图:
我输入的参数是MAX(2.1,5);根据上面的编译结果可以看到确实有了指针类型不同的警告。
第一章讲解完成之后是不是特别佩服LinuxKernel的贡献者,所以接下来在学习Linux的过程中我会不断地找到一些很棒的Linux的宏定义来和大家分享。