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

chmurli 1627 c linux macros linux-kernel

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

怎么:-!!办?

John Feminel.. 1655

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

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

你应该读这样的表达式:

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

  2. !!(e):逻辑否定两次:0if 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()是一个运行时测试.

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

  • Linux内核不使用C++,至少在Linus还活着的时候不会. (226认同)
  • 最近的C++或C标准变体有类似于`static_assert`的相关用途. (158认同)
  • 值得注意的是,`!! e`不会评估为零"或非零正数",而是具体为零或一.C中的布尔表达式定义为始终求值为零或一. (147认同)
  • @Lundin - #error需要使用3行代码#if /#error/#endif,并且只能用于预处理器可访问的评估.此hack适用于编译器可访问的任何评估. (54认同)
  • 我也想知道这一点,特别是当我明确地说0或1时,但我认为你写的就像一个纯粹的数学家,声称足以证明这一点,并且只是必要的. (9认同)
  • @weston很多不同的地方.**[亲自看看!](http://git.kernel.org/?p=linux%2Fkernel%2Fgit%2Ftorvalds%2Flinux.git&a=search&h=HEAD&st=grep&s=BUILD_BUG_ON_ZERO)** (5认同)
  • @Dolda2000:"*C中的布尔表达式定义为始终评估为零或一*" - 不完全相同.产生"逻辑布尔"结果的*运算符*(`!`,`<`,`>`,`<=`,`> =`,`==`,`!=`,`&&`,`| |`)总是产生0或1.其他表达式可能产生可用作条件的结果,但只是零或非零; 例如,`isdigit(c)`,其中`c`是一个数字,可以产生*任何*非零值(然后在条件中将其视为真). (5认同)
  • 这就是D中引入静态断言的原因 (4认同)
  • 因为什么时候`sizeof`允许任何类型为零?这违反了数组元素地址的唯一性.(这是C,所以我们不要进入空基类优化,这仍然不会导致任何对象的大小为零) (4认同)
  • re:static_assert,这在Linux构建的任何地方都不可用. (3认同)
  • @Dolda2000:+1.同意,这可能是措辞不佳.我只是在这里指出,它解决的具体数字并不重要,只要它是一个非零正数.你可以在第5步看到我使用'1`的确切值. (3认同)
  • 此测试的另一个重点是表达式是否可以在编译时进行评估,如果不能,则构建将失败. (3认同)
  • 关于名称的快速说明.它被称为`... ON_ZERO`,因为它是`BUG_ON`的衍生物,一个宏本质上是一个断言.`BUG_ON(foo)`表示"如果`foo`为真,那就是一个错误"(在运行时).相反,`BUILD_BUG_ON`是一个静态断言(在构建时检查),最后`BUILD_BUG_ON_ZERO`完全相同,除了整个东西是一个等于`(size_t)0`的表达式,因为问题中的注释表明. (3认同)
  • 那么如何使用这个宏,即使用`e`的值或表达式? (2认同)
  • @BenVoigt C标准不允许零大小的类型.Linux内核依赖于许多不属于标准的`gcc`编译器扩展.参见,例如,这里:http://stackoverflow.com/questions/8143417/why-is-linux-kernel-coded-using-non-standard-c-gcc-specific-features (2认同)
  • @John:我知道内核使用gcc扩展.但扩展仅仅为无效代码赋予了意义.如果编译器改变了有效代码的含义,那就是一个bug. (2认同)
  • 我从事C编程已有一段时间了,还没有看到这种“位域”功能。因此,对于处于同一职位的任何人,这里都有一个很好的教程:http://www.tutorialspoint.com/cprogramming/c_bit_fields.htm (2认同)

David Heffer.. 252

:是一个位域.至于那!!,这是逻辑上的双重否定,因此返回0虚假或1真实.这-是一个减号,即算术否定.

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

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

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

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

  • 是`sizeof(struct {int:0;})`严格符合? (14认同)
  • @DavidHeffernan实际上C允许"0"宽度的未命名位域,但如果结构中没有其他命名成员则不允许.`(C99,6.7.2.1p2)"如果struct-declaration-list不包含命名成员,则行为是未定义的."`例如`sizeof(struct {int a:1; int:0;})`是严格符合但是`sizeof(struct {int:0;})`不是(未定义的行为). (8认同)
  • 为什么结果一般为"0"?只有空位域的`struct`,为true,但我认为不允许使用大小为0的结构.例如,如果您要创建该类型的数组,那么各个数组元素仍然必须具有不同的地址,不是吗? (7认同)
  • @ouah关于未命名的零长度位域,请参见此处:http://stackoverflow.com/questions/4297095/practical-use-of-zero-length-bitfields#4297110 (3认同)
  • 他们实际上并不关心使用GNU扩展,他们禁用严格的别名规则,不考虑整数溢出为UB.但我想知道这是否严格符合C. (2认同)
  • @ouah C标准中也有相同的文本. (2认同)

keithmo.. 164

有些人似乎对这些宏感到困惑assert().

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


Daniel Santo.. 50

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

#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函数属性,允许您扩展这个旧概念,但生成编译时错误,并带有您选择的消息 - 不再是神秘的"负大小的数组" "错误信息!

#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都已相应更新.但是,此宏不能用作初始化程序.但是,使用by 语句表达式(另一个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上使用.人.

  • 您的所有解决方案都不是替代方案.宏上面的注释非常清楚"`所以表达式可以用在例如结构初始化器中(或者其他地方不允许使用逗号表达式).""宏返回类型为`size_t的表达式 (6认同)
  • @Wiz是的,我知道这一点.也许这有点冗长,也许我需要重新访问我的措辞,但我的观点是探索静态断言的各种机制,并说明为什么我们仍然使用负大小的位域.简而言之,如果我们得到一个常量语句表达式的机制,我们将打开其他选项. (3认同)

Matt Phillip.. 35

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

  • 实际上,如果条件为真,它返回一个值为0的`size_t`. (3认同)

归档时间:

查看次数:

179410 次

最近记录:

2 年,7 月 前