C语言中的“:-!!”是什么?

What is ':-!!' in C?

提问人:chmurli 提问时间:2/10/2012 最后编辑:Jan Schultkechmurli 更新时间:8/26/2023 访问量:202107

问:

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

该怎么办?:-!!


更新:最近,该宏已移至 /usr/include/linux/build_bug.h

C Linux 宏运 算符

评论

83赞 Niklas B. 2/11/2012
git blame 告诉我们,这种特殊形式的静态断言是由 Jan Beulich 在 8c87df4 中引入的。显然,他有充分的理由这样做(请参阅提交消息)。
2赞 CyrillC 2/10/2012
- 一元减去 <br /> !逻辑 NOT <br /> 不是给定整数 e 的反 NOT,因此变量可以是 0 或 1。
0赞 Phillip Cloud 3/11/2012
等等,我以为没有评估的论点。在这种情况下,这是错的吗?如果是这样,为什么?因为它是宏?sizeof
3赞 Winston Ewert 3/25/2012
@cpcloud,确实“评估”了类型,而不是值。在这种情况下,它是无效的类型。sizeof
2赞 phorgan1 2/16/2012
不言而喻,创建的位域是匿名的。这与 C++ 模板元编程的精神相同,即在编译时发生可以在编译时检查的事情。

答:

274赞 David Heffernan 2/10/2012 #1

是一个位字段。至于 ,这是逻辑上的双重否定,因此返回 false 或 true。而 the 是一个减号,即算术否定。:!!01-

这一切都只是一个技巧,让编译器对无效的输入进行barf。

考虑。当计算结果为负值时,将产生编译错误。否则,计算结果为 0,并且 0 宽度位域的大小为 0。因此,宏的计算结果为值为 0 的 a。BUILD_BUG_ON_ZERO-!!(e)-!!(e)size_t

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

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

评论

17赞 ouah 2/10/2012
是否严格遵守?sizeof(struct { int:0; })
8赞 Jens Gustedt 2/10/2012
为什么结果一般是?只有一个空位域的 A,true,但我认为不允许使用大小为 0 的结构。例如,如果您要创建该类型的数组,则各个数组元素仍然必须具有不同的地址,不是吗?0struct
2赞 ouah 2/10/2012
他们实际上并不关心,因为他们使用 GNU 扩展,他们禁用严格的别名规则,并且不将整数溢出视为 UB。但我想知道这是否严格符合 C。
3赞 David Heffernan 2/10/2012
@ouah关于未命名的零长度位域,请参阅此处:stackoverflow.com/questions/4297095/...
11赞 ouah 10/16/2012
@DavidHeffernan实际上 C 允许宽度的 unamed 位域,但如果结构中没有其他命名成员,则不允许。 因此,例如,严格遵守但不是(未定义的行为)。0(C99, 6.7.2.1p2) "If the struct-declaration-list contains no named members, the behavior is undefined."sizeof (struct {int a:1; int:0;})sizeof(struct { int:0; })
39赞 Matt Phillips 2/10/2012 #2

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

评论

3赞 David Heffernan 2/10/2012
实际上,如果条件为真,它会返回值为 0 的值。size_t
1806赞 John Feminella 2/10/2012 #3

实际上,这是一种检查表达式 e 是否可以计算为 0 的方法,如果不能,则测试生成失败

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

您应该这样阅读表达式:

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

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

  3. -!!(e):从数字上否定步骤 2 中的表达式:如果是 ;否则。00-1

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

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

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


有人问:为什么不直接使用断言呢?

Keithmo在这里的回答有一个很好的回应:

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

完全正确。您不希望在运行时检测到内核中可能更早发现的问题!它是操作系统的关键部分。无论在编译时可以检测到什么程度的问题,都更好。

评论

198赞 Basile Starynkevitch 2/11/2012
C++ 或 C 标准的最新变体具有类似目的的东西。static_assert
60赞 Ed Staub 2/11/2012
@Lundin - #error 需要使用 3 行代码 #if/#error/#endif,并且仅适用于预处理器可访问的评估。这个 hack 适用于编译器可访问的任何评估。
261赞 Mark Ransom 2/11/2012
Linux 内核不使用 C++,至少在 Linus 还活着的时候不使用。
10赞 Keith Thompson 4/3/2013
@Dolda2000:“C 语言中的布尔表达式被定义为始终计算为 0 或 1”——不完全是。产生“逻辑布尔”结果的运算符(、、、、)总是产生 0 或 1。其他表达式可能会产生可用作条件的结果,但仅为零或非零;例如,,其中 是数字,可以生成任何非零值(然后在条件中将其视为 true)。!<><=>===!=&&||isdigit(c)c
9赞 Xion 11/22/2016
关于名称的快速说明。之所以这样称呼它,是因为它是 的派生词,而宏本质上是一个断言。 表示“如果为 true,则为错误”(在运行时)。相反,是一个静态断言(在构建时检查),最后完全相同,只是整个事情是一个等于 的表达式,正如问题中的注释所述。...ON_ZEROBUG_ONBUG_ON(foo)fooBUILD_BUG_ONBUILD_BUG_ON_ZERO(size_t)0
176赞 keithmo 2/10/2012 #4

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

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

更新:

从 C11 开始,_Static_assert() 关键字可用于创建编译时测试,除非正在为旧编译器编写代码,否则应使用该关键字。

58赞 Daniel Santos 6/27/2013 #5

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

#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 扩展),您可以!

#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 或不是编译时常量,则显示“我告诉过你不要给我 5!

那么,我们为什么不使用它来代替负大小的位域呢?唉,目前语句表达式的使用有很多限制,包括它们用作常量初始值设定项(用于枚举常量、位域宽度等),即使语句表达式本身是完全恒定的(即,可以在编译时完全计算并通过 __builtin_constant_p() 测试)。此外,它们不能在函数体之外使用。

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

评论

6赞 Wiz 7/23/2013
您的所有解决方案都不是替代品。宏上方的注释非常清楚 “” 宏返回一个类型的表达式so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted).size_t
3赞 Daniel Santos 7/24/2013
@Wiz 是的,我知道这一点。也许这有点冗长,也许我需要重新审视我的措辞,但我的观点是探索静态断言的各种机制,并说明为什么我们仍然使用负大小的位域。简而言之,如果我们得到一个常量语句表达式的机制,我们将打开其他选项。
0赞 Karthik Raj Palanichamy 3/24/2017
无论如何,我们不能将这些宏用于变量。右? 它只允许常量。那么,有什么用呢?error: bit-field ‘<anonymous>’ width not an integer constant
1赞 Daniel Santos 3/26/2017
@Karthik 搜索 Linux 内核的源代码,了解使用它的原因。