提问



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


/* 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。 (关于这是否是一个令人困惑的名称偶尔会有讨论。)[48]


你应该读这样的表达式:


sizeof(struct { int: -!!(e); }))



  1. (e):计算表达式e

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

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

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

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



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





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


基思莫的答案在这里得到了很好的回应:



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



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

其它参考1


:是一个位域。至于!!,这是逻辑双重否定,因此返回0表示错误,1表示真实。并且-是减号,即算术否定。


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


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

其它参考2

size_t


我的视图中名称很弱,因为当输入不为零时,构建实际上会失败。


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

其它参考3


有些人似乎在混淆这些宏assert()


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

其它参考4


好吧,我很惊讶没有提到这种语法的替代方案。另一个常见(但更旧)的机制是调用一个未定义的函数,如果你的断言正确,依赖于优化器来编译函数调用。


#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中停止破坏构建)这样的技巧。


为了满足编译时断言的需要,GCC 4.3引入了error函数属性,该属性允许您扩展这个旧概念,但生成编译时错误并带有您选择的消息 - 不再神秘的负大小数组错误消息![51]


#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的宏,它使用了这个功能,bug.h中的大多数宏都已相应更新。尽管如此,这个宏不能用作初始化器。但是,使用语句表达式(另一个GCC C扩展),你可以![52] [53] [54]


#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()测试)。此外,它们不能在函数体外使用。[55]


希望GCC能够尽快修改这些缺点,并允许将常量语句表达式用作常量初始化器。这里的挑战是定义什么是合法常量表达式的语言规范。 C ++ 11为这种类型或事物添加了constexpr关键字,但C11中没有对应关系。虽然C11确实得到了静态断言,这将解决部分问题,但它不会解决所有这些缺点。因此,我希望gcc可以通过-std=gnuc99& amp;来提供constexpr功能作为扩展。 -std=gnuc11或其他一些,并允许它在语句表达式et上使用。人。

其它参考5


如果条件为假,则创建一个大小0位域,如果条件为真/非零,则创建大小-1(-!!1)位域。在前一种情况下,没有错误,结构用int成员初始化。在后一种情况下,存在编译错误(当然没有创建大小-1位域的东西)。

其它参考6


 Linux Kernel :   

/* 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); }))