C语言宏定义-max、min宏再看Linux内核问题演进
1. 简介
1) 通常我们经常使用如下宏判断返回X、Y的最小值或者最大值
#define min(X, Y) ((X) < (Y) ? (X) : (Y))
#define max(X, Y) ((X) < (Y) ? (Y) : (X))
- 宏参数可能被重复调用的问题:例如
next=min(++a,1)
,宏被展开以后等于next=((++a) < (1)) ? (++a) : (1))
,看出来宏的参数++a
被调用了2次,又如:next = min (x + y, foo (z));
,宏体被展开成next = ((x + y) < (foo (z)) ? (x + y) : (foo (z)))
,foo(z)
也被调用了两次,可能程序会产生逻辑错误,不是我们期待的值。
2) 再来看看Linux 内核定义怎么解决这个问题的
#define min(a, b) ({ \
typeof(a) __min1__ = (a); \
typeof(b) __min2__ = (b); \
(void)(&__min1__ == &__min2__); \
__min1__ < __min2__ ? __min1__ : __min2__;})
-
用到了GCC的一个扩展特性
({statement list})
,形如({ ... })
这样的代码块会被视为一条语句,其计算结果是{ ... }
中最后一条语句的计算结果 -
先根据a, b的类型生成了两个局部变量
__min1__
和__min2__
,之后比较其大小,返回较小的一个,这样就保证了宏参数只会被执行一次,避免了上述参数两次被执行的副作用 -
(void)(&__min1__ == &__min2__)
判断参数a、b类型是否相同,不相同编译器将产生警告,相当美妙
3) 看到这里认为一切就好了吗,继续看下去,膜拜一下Linux内核开发者的严谨性
我们先考虑一种情况,假如外部使用参数名称和min
宏定义实现中的局部变量__min1__
__min2__
重名了,会发生什么情况,例如
int __min1__ = 10;
int __min2__ = 20;
int min_val = min(__min1__--, __min2__++);
我们期待的结果:__min1__ = 9
, __min2__ = 21
, min_val = 10
,但是最后情况是__min1__ = 10
, __min2__ = 20
,min_val
未知。造成这一结果的原因在于输入参数和宏定义内部使用的局部变量重名了,这样就会导致在宏定义的语句块内,外层同名变量的作用域被内层局部变量的作用域所屏蔽,展开后的代码就成了这样:
int main_val = ({typeof(__min1__--) __min1__ = (__min1__--); /* 省略 */ })
这就类似于int a = a
这样的语句,执行完后a的值是不确定的,且因为展开后__min1__
成了宏体内的局部变量,__min1__--
的自减操作对于外层变量来说也是无效的。
其实最初宏定义中使用__min1__
这样的名字也是为了避免重名,但是也可能存在重名的风险,内核引入了__UNIQUE_ID
可以生成一个唯一的名字。唯一性是由编译器提供的__COUNTER__
宏来保证,当然我们编写代码不应该再使用类似__UNIQUE_ID_min1_0
变量名称,这样刻意造成重名。
/* Indirect macros required for expanded argument pasting, eg. __LINE__. */
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)
#define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__)
/*
* min()/max()/clamp() macros that also do
* strict type-checking.. See the
* "unnecessary" pointer comparison.
*/
#define __min(t1, t2, min1, min2, x, y) ({ \
t1 min1 = (x); \
t2 min2 = (y); \
(void) (&min1 == &min2); \
min1 < min2 ? min1 : min2; })
#define min(x, y) \
__min(typeof(x), typeof(y), \
__UNIQUE_ID(min1_), __UNIQUE_ID(min2_), \
x, y)
4)max宏,在后面内核有进行过一轮整改和演进,具体查看下面文章分析:
2. __COUNTER__
宏
__COUNTER__
宏是GCC GNU的一种扩展,会被展开为一个从0开始的整数,且每次调用后其值都会加一,这也就保证了其唯一性。
根据 GCC.GNU 的文档
3. 相关宏
CMO_MIN(x, y) | 返回x、y中最小值 |
---|---|
CMO_MAX(x, y) | 返回x、y中最大值 |
CMO_ABS(x) | 返回x的绝对值 |
CMO_CLAMP(x, tmin, tmax) | 用来将某个值“钳”住、返回值限制在[tmin, tmax]这个区间范围内 |
CMO_INRANAGE(x, tmin, tmax) | 如果x在[tmin, tmax]内返回true,否则返回false |
/**
* @def CMO_MIN
* @brief Return smaller value of two provided expressions.
*/
/**
* @def CMO_MAX
* @brief Return larger value of two provided expressions.
*/
#if (CMO__HAVE_TYPEOF == 1)
#define CMO_MIN(x, y) \
({ \
typeof(x) ____min_x_ = (x); \
typeof(y) ____min_y_ = (y); \
(void)(&____min_x_ == &____min_y_); \
____min_x_ < ____min_y_ ? ____min_x_ : ____min_y_; \
})
#define CMO_MAX(x, y) \
({ \
typeof(x) ____max_x_ = (x); \
typeof(y) ____max_y_ = (y); \
(void)(&____max_x_ == &____max_y_); \
____max_x_ > ____max_y_ ? ____max_x_ : ____max_y_; \
})
#else
#define CMO_MIN(x, y) ((x) < (y) ? (x) : (y))
#define CMO_MAX(x, y) ((x) < (y) ? (y) : (x))
#endif /* (CMO__HAVE_TYPEOF == 1) */
/**
* @def CMO_ABS
* @brief Return absolute value of the provided expressions.
*/
#define CMO_ABS(x) ((x) > 0 ? (x) : -(x))
/**
* @def CMO_CLAMP
* @brief Clamp one number between a min and max.
*
* ```c
* if (x < tmin) return tmin;
* else if (x > tmax) return tmax;
* else return x;
* ```
*/
#define CMO_CLAMP(x, tmin, tmax) (CMO_MAX(CMO_MIN((x), (tmax)), (tmin)))
/**
* @def CMO_INRANAGE
* @brief Return true, when one number between a min and max, otherwise false.
*/
#define CMO_INRANAGE(x, tmin, tmax) ((x) >= (tmin) && (x) <= (tmax))