协慌网

登录 贡献 社区

什么是 ”:-!!” 用 C 代码?

我在/usr/include/linux/kernel.h 中碰到了这个奇怪的宏代码:

/* Force a compilation error if condition is true, but also produce a
   result (of value 0 and type size_t), so the expression can be used
   e.g. in a structure initializer (or where-ever else comma expressions
   aren't permitted). */
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))

什么:-!!做?

答案

实际上,这是一种检查表达式 e 是否可以被评估为 0 的方法,如果不是,则检查构建是否失败

这个宏有点名不副实; 它应该更像BUILD_BUG_OR_ZERO ,而不是...ON_ZERO 。 ( 偶尔会讨论这是否是一个令人困惑的名字 。)

你应该读这样的表达式:

sizeof(struct { int: -!!(e); }))
  1. (e) :计算表达式e

  2. !!(e) :逻辑否定两次: 0如果e == 0 ; 否则1

  3. -!!(e) :数字否定步骤 2 中的表达式: 0如果为0 ; 否则-1

  4. struct{int: -!!(0);} --> struct{int: 0;} :如果它为零,那么我们声明一个结构,其中包含一个宽度为零的匿名整数位域。一切都很好,我们正常进行。

  5. struct{int: -!!(1);} --> struct{int: -1;} :另一方面,如果它为零,那么它将是一些负数。声明任何具有宽度的位域是编译错误。

因此,我们要么结束一个结构中宽度为 0 的位域,这很好,要么是负宽度的位域,这是一个编译错误。然后我们获取该字段的sizeof ,因此我们得到一个具有适当宽度的size_t (在e为零的情况下将为零)。


有人问: 为什么不使用assert呢?

keithmo 的答案在这里得到了很好的回应:

这些宏实现了编译时测试,而 assert()是一个运行时测试。

非常正确。您不希望在运行时检测到内核中可能早先发现的问题!它是操作系统的关键部分。无论在何种程度上,在编译时都可以检测到问题,那就更好了。

:是一个位域。至于!! ,这是逻辑双重否定 ,因此返回0表示 false 或1表示 true。而-是一个减号,即算术否定。

这只是让编译器对无效输入进行 barf 的技巧。

考虑BUILD_BUG_ON_ZERO 。当-!!(e)求值为负值时,会产生编译错误。否则-!!(e)求值为 0,0 宽度位域的大小为 0. 因此宏评估值为 0 的size_t

在我看来,这个名称很弱,因为当输入为零时,构建实际上会失败。

BUILD_BUG_ON_NULL非常相似,但产生指针而不是int

有些人似乎将这些宏与assert()混淆。

这些宏实现了编译时测试,而assert()是一个运行时测试。