我碰到了/usr/include/linux/kernel.h中的这个奇怪的宏代码: html
/* 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); }))
有什么用:-!!
作? linux
好吧,我很惊讶没有提到该语法的替代方法。 另外一种常见的(但较旧的)机制是调用未定义的函数,若是断言正确,则依靠优化器来编译出该函数调用。 git
#define MY_COMPILETIME_ASSERT(test) \ do { \ extern void you_did_something_bad(void); \ if (!(test)) \ you_did_something_bad(void); \ } while (0)
虽然此机制有效(只要启用了优化),但它的缺点是直到您连接后才报告错误,这时它将没法找到函数you_did_something_bad()的定义。 这就是内核开发人员开始使用诸如负数大小的位域宽度和负数大小的数组之类的技巧的缘由(后者后来中止了破坏GCC 4.4中的构建)。 express
出于对编译时断言的需求的同情,GCC 4.3引入了error
函数属性 ,该属性使您能够扩展这个较旧的概念,但会生成编译时错误,并带有您选择的消息-再也不用神秘的“负数大小”阵列”错误消息! 数组
#define MAKE_SURE_THIS_IS_FIVE(number) \ do { \ extern void this_isnt_five(void) __attribute__((error( \ "I asked for five and you gave me " #number))); \ if ((number) != 5) \ this_isnt_five(); \ } while (0)
事实上,因为Linux 3.9的,咱们如今有一个宏叫compiletime_assert
使用这种功能和最in的宏bug.h
已进行相应更新。 尽管如此,该宏仍不能用做初始化程序。 可是,使用by 语句表达式 (另外一个GCC C扩展名),您能够! 函数
#define ANY_NUMBER_BUT_FIVE(number) \ ({ \ typeof(number) n = (number); \ extern void this_number_is_five(void) __attribute__(( \ error("I told you not to give me a five!"))); \ if (n == 5) \ this_number_is_five(); \ n; \ })
该宏将只对它的参数进行一次评估(以防它产生反作用),并产生一个编译时错误,提示“我告诉过你不要给我五个!” 若是表达式的计算结果为5或不是编译时常数。 测试
那么,为何不使用这个而不是负大小的位域呢? 遗憾的是,当前使用语句表达式有不少限制,包括将它们用做常量初始化程序(用于枚举常量,位域宽度等),即便语句表达式自己是彻底恒定的(便可以彻底求值)在编译时,不然经过__builtin_constant_p()
测试)。 此外,它们不能在功能主体以外使用。 优化
但愿GCC会尽快修正这些缺点,并容许将常量语句表达式用做常量初始化程序。 这里的挑战是定义什么是合法常量表达式的语言规范。 C ++ 11仅为此类型或事物添加了constexpr关键字,但C11中没有对应的关键字。 尽管C11确实得到了静态断言,这将解决部分问题,但它没法解决全部这些缺点。 所以,我但愿gcc能够经过-std = gnuc99&-std = gnuc11或相似的方式使constexpr功能做为扩展使用,并容许其在语句表达式等上使用。 等 ui
:
是位域。 至于!!
,这是逻辑上的双重否认 ,所以返回0
表示false或1
表示true。 -
是减号,即算术求反。 this
这只是使编译器对无效输入bar之以鼻的一种技巧。
考虑BUILD_BUG_ON_ZERO
。 当-!!(e)
计算为负值时,将产生编译错误。 不然, -!!(e)
值为0,而且宽度为0的位字段的大小为0。所以,宏的值的值为size_t
,值为0。
在我看来,这个名称很薄弱,由于当输入不为零时,构建实际上会失败。
BUILD_BUG_ON_NULL
很是类似,可是产生一个指针而不是一个int
。
若是条件为假,它将建立大小为0
位域,但若是条件为真/非零,则建立大小为-1
( -!!1
)的位域。 在前一种状况下,没有错误,而且使用int成员初始化了该结构。 在后一种状况下,会出现编译错误(固然不会建立大小为-1
位域)。
实际上,这是一种检查表达式e是否能够评估为0的方法,若是不能,则使build失败 。
该宏的名称有些错误; 它应该更像BUILD_BUG_OR_ZERO
,而不是...ON_ZERO
。 ( 偶尔会讨论这个名称是否使人困惑 。)
您应该这样阅读表达式:
sizeof(struct { int: -!!(e); }))
(e)
:计算表达式e
。
!!(e)
在逻辑否认两次: 0
若是e == 0
; 不然1
。
-!!(e)
在数字上否认表达来自步骤2: 0
,若是它是0
; 不然为-1
。
struct{int: -!!(0);} --> struct{int: 0;}
:若是它为零,则咱们声明一个结构,该结构具备一个宽度为零的匿名整数位字段。 一切都很好,咱们会照常进行。
struct{int: -!!(1);} --> struct{int: -1;}
:另外一方面,若是它不为零,则它将为负数。 声明任何宽度为负的位字段都是编译错误。
所以,咱们要么在结构中使用宽度为0的位域(这很好),要么使用宽度为负的位域(这是编译错误)结束。 而后,咱们使用该字段的sizeof
,从而得到具备适当宽度的size_t
(在e
为零的状况下为零)。
有人问: 为何不仅使用assert
?
keithmo的回答在这里获得了很好的回应:
这些宏实现编译时测试,而assert()是运行时测试。
很是正确。 您不想在运行时检测内核中可能早已发现的问题! 这是操做系统的关键部分。 不管在何种程度上能够在编译时检测到问题,都更好。
有些人彷佛将这些宏与assert()
混淆了。
这些宏实现编译时测试,而assert()
是运行时测试。