在分支预测,hash value计算,tree insert的时候,经常需要比较大小。这是一个高频率事件。 计算准确性,关系到系统稳定性。
普通程序员,写的关系运算符宏
#define min(a,b) ((a) < (b) ? (a) : (b))

linux kernel写的关系运算符宏:
c
/* Format: __UNIQUE_ID_<name>_<__COUNTER__> */
#define __UNIQUE_ID(name) \
__PASTE(__UNIQUE_ID_, \
__PASTE(name, \
__PASTE(_, __COUNTER__)))
#define __cmp_op_min <
#define __cmp_op_max >
#define __cmp(op, x, y) ((x) __cmp_op_##op (y) ? (x) : (y))
#define __cmp_once_unique(op, type, x, y, ux, uy) \
({ type ux = (x); type uy = (y); __cmp(op, ux, uy); })
#define __cmp_once(op, type, x, y) \
__cmp_once_unique(op, type, x, y, __UNIQUE_ID(x_), __UNIQUE_ID(y_))
/**
* min_t - return minimum of two values, using the specified type
* @type: data type to use
* @x: first value
* @y: second value
*/
#define min_t(type, x, y) __cmp_once(min, type, x, y)
static ssize_t kernfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
struct kernfs_open_file *of = kernfs_of(iocb->ki_filp);
ssize_t len = min_t(size_t, iov_iter_count(iter), PAGE_SIZE);
const struct kernfs_ops *ops;
char *buf;
...
}
假设iov_iter_count(iter)的当前值为3,PAGE_SIZE的值为4096
其中的一行代码 ssize_t len = min_t(size_t, iov_iter_count(iter), PAGE_SIZE); 被展开之后是:
c
ssize_t len = ({
size_t __UNIQUE_ID_x____COUNTER__ = (iov_iter_count(iter));
size_t __UNIQUE_ID_y____COUNTER__ = (PAGE_SIZE);
((__UNIQUE_ID_3____COUNTER__) < (__UNIQUE_ID_4096____COUNTER__) ? : __UNIQUE_ID_3____COUNTER__, __UNIQUE_ID_4096____COUNTER__)
});
为啥费劲巴拉,搞这么复杂,内核开发者是有病吗? 显然不是。 有2个bug:
bug 1,重复求值:
众所周知,宏是string_a替换为string_b,不带任何逻辑推导功能
min(++a, b); 作者原本的意思是: a=a+1; (a<b)? a : b // a自增一次之后,与b比较,求a和b的最小值
但宏被展开之后: (++a < b) ? ++a : b // a 会自增两次! 而不是自增1次
bug 2,数据类型隐式转换:
c
int a = -5;
unsigned int b = 100;
unsigned int r = min(a, b); // 人脑预期,r = -5
// gcc 编译器的操作,把-5从int类型提升为unsigned int类型,a = UINT_MAX - 4, 显然a > 100,计算结果r = 100
bug 3,副作用问题:
如果用这样的宏定义,可以避免bug1、bug2
c
#define min_t(type, x, y) ({ \
type _x = x; \
type _y = y; \
_x < _y ? _x : _y; \
})
// 但同一函数里写两次 min_t,编译器就会报_x, _y多次定义的错误
c
size_t a = min_t(size_t, v1, v2);
size_t b = min_t(size_t, v3, v4);
因此,内核开发者苦心孤诣,搞了这么一套复杂、灵活的宏,彻底解决了上面3个bug
花费2个小时才看懂的设计。
普通程序员,一行代码搞定。
(又蠢又自信那种,清澈的蠢)
linux kernel程序员,来一套复杂的组合拳,精准、全面、无bug。
(算尽天机不自持,方兴未艾或可知)
这个就是差距所在。2类程序员有云泥之别。