首页 文章

C代码中的“: - !!”是什么?

提问于
浏览
1529

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

:-!! 做什么?

5 回答

  • 1569

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

  • 244

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

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

  • 43

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

    #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 function attribute,它允许您扩展这个较旧的概念,但会产生编译时错误,并带有您选择的消息 - 不再有神秘的错误消息! - 457266_错误消息!

    #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中的大多数宏都已相应更新 . 但是,此宏不能用作初始化程序 . 但是,使用statement expressions(另一个GCC C-extension),你可以!

    #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功能作为扩展提供,并允许它在语句表达式et上使用 . 人 .

  • 153

    实际上,这是 a way to check whether the expression e can be evaluated to be 0, and if not, to fail the build .

    这个宏有点名不副实;它应该更像 BUILD_BUG_OR_ZERO ,而不是 ...ON_ZERO . (已有 occasional discussions about whether this is a confusing name . )

    你应该读这样的表达式:

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

    • !!(e) :逻辑否定两次: 0 if 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 为零的情况下它将为零) .


    有人问: Why not just use an assert?

    keithmo's answer这里有一个很好的回应:

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

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

  • 32

    : 是一个位域 . 至于 !! ,即logical double negation,因此返回 0 表示false或 1 表示true . 并且 - 是减号,即算术否定 .

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

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

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

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

相关问题