非技术性文章,只是为了分享解决问题的方法
小白在复习C语言数据结构查看Linux源代码的时候,看到了Linux源代码中MAX(a,b)的实现,它能够比较2个任意类型的数据(可行的情况下)并获得最大值,实现得非常通用。Linux是利用宏,并且使用了GNU的扩展语法:typeof()和语句表达式实现的,代码如下:
#define MAX(a,b) \
({\
typeof(a) _a = a;\
typeof(b) _b = b;\
(void)(&_a == &_b);\
((_a)>(_b)?(_a):(_b));\
})
扩展语法:
typeof()
出现在3行,很好理解,typeof(a)就是获得变量a的类型,这样做的目的有2个,①实现任意类型的比较,使得用户的可选择性更大,对数据类型的包容性更高。②是为了使得宏参数在宏定义中只出现一次,避免在使用MAX()的时候宏参数出现二义性,比如MAX(a++,b)如果没有3-4行,宏展开的时候,a++会出现在多个地方,结果会出人意料,有疑惑的可以上机实现。
( { } )
这个东东出现在2行-7行,比较难以接受,它是GNU的扩展语法,叫做语句表达式,内层的{ }表示一个复合语句,外层的括号()目的是为了符合C语言中“复合语句不能出现在表达式中”的语法规定,既然{ }是复合语句,那么再加一层括号()使得其变为表达式,没错,就是这么拗口。一般用法为var = ( { others;(val) ;} ),其中的others;就是一系列的完整语句,(val)是必须的,它作为语句表达式最后的值,因为C语句规定每一个表达式到最后都必须有一个值,对应着上面代码6行带三目运算符的表达式 ,也就是实际求出最大值的表达式。
现在有一个相当令人费解的语句,它就是5行的:(void)(&_a == &_b);
在解决这个语句之前,小白突然想起自己学ZigBee协议栈的时候,也出现了类似的语句,查找ZigBee协议栈的实现,发现了如下代码:
uint16 HalUARTWrite(uint8 port, uint8 *buf, uint16 len)
{
(void)port;
(void)buf;
(void)len;
#if (HAL_UART_DMA == 1)
if (port == HAL_UART_PORT_0) return HalUARTWriteDMA(buf, len);
……
}
这是hal_uart.c函数,其中出现了(void)xxx的语句,相当疑惑,最后发现其作用:占位!!
(void)object的作用:消除编译器对object没有引用的警告
由于ZigBee协议栈只是提供了基本的网络通讯和底层初始化实现,当自己需要特定功能的时候,往往要修改某些函数,而这些函数的函数签名是不能改变的,其中之一就是参数列表是不能修改的,而有时候自己的函数并不需要用到某些参数,那么这些参数在函数中就相当于没有被引用,根据C语音的语法检查,当一个变量没有被引用的时候,编译器会给出警告,作为例子而言,在Qt下的一个C程序:
int main(int argc, char *argv[])
{
int ago = 100;
int min = -1;
printf("%d",ago); //只引用ago,没有引用argc\argv\min
return 0;
}
由于main参数argc和argc以及变量min都没有被引用,因此编译器出现以下警告:
而ZigBee函数非常多,如果自己修改的函数通通给出警告,那么编译的时候是非常令人烦恼的,程序没有出错但是警告一大堆,为了屏蔽这些警告,需要对这些可能没有引用的变量占位,占位也就是无意义地去引用变量,(void)port就是把port变量类型强制转换为void但是没有下一步操作,这个语句对变量引用是无意义的,但是确实引用了变量,编译器就不会给出警告。
我们修改一下程序:
int main(int argc, char *argv[])
{
int ago = 100;
int min = -1;
(void)argc;
(void)argv;
(void)min;
printf("%d",ago);
return 0;
}
再次运行程序,无任何警告:
至于有人问为什么是(void),而不是(int)/(char)等等?答案是,无关紧要,只要你愿意,完全可以用任何类型,但是由于大家都直接用(void),因此(void)也变成了不成文的规定,就像头文件中防止多重包含的 #ifndef _NAME_H_等,为什么基本都是这种写法,其它写法不可以吗??当然可以,只是大家都这样做,成为了不成文规定,你也应该跟着这样做,否则别人阅读你的代码时会显得很疑惑。
Linux的(void)(&_a == &_b)作用:消除编译器&_a == &_b没有被引用的警告,同时可能得到类型不一致的警告
言归正传,仔细观察这个语句:(void)(&_a == &_b); ,它的取出_a和_b的地址进行比较,然后得到一个bool值,接着进行强制类型转换这一个无意义的变量引用操作。
这里的目的是为了给出有用的警告,因为Linux内核的这个MAX函数是实现2个任意类型的数据比较并获得最大值,当实际的两个数据类型不一样的时候,或许它们之间确实能够比较,就像100和12.2,但是为了提醒用户它们类型不一样,需要把两个数据的地址进行比较(&_a == &_b),不过单纯的地址比较对编译器而言并没有意义,编译器会觉得这是一个没有引用的内容,会给出额外的警告,而Linux只是想给出类型不一致的警告,并不想要其它警告,所以在地址比较后进行强制类型转换,消除了没有被引用的警告,得到可能类型不一致的警告。