提问人:chmurli 提问时间:2/10/2012 最后编辑:Jan Schultkechmurli 更新时间:8/26/2023 访问量:202107
C语言中的“:-!!”是什么?
What is ':-!!' in C?
问:
我在 /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
答:
是一个位字段。至于 ,这是逻辑上的双重否定,因此返回 false 或 true。而 the 是一个减号,即算术否定。:
!!
0
1
-
这一切都只是一个技巧,让编译器对无效的输入进行barf。
考虑。当计算结果为负值时,将产生编译错误。否则,计算结果为 0,并且 0 宽度位域的大小为 0。因此,宏的计算结果为值为 0 的 a。BUILD_BUG_ON_ZERO
-!!(e)
-!!(e)
size_t
在我看来,这个名字很弱,因为当输入不为零时,构建实际上会失败。
BUILD_BUG_ON_NULL
非常相似,但生成的是指针而不是 .int
评论
sizeof(struct { int:0; })
0
struct
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; })
如果条件为 false,则创建一个大小位域,但如果条件为 true/非零,则创建一个大小 () 位域。在前一种情况下,没有错误,并且结构是使用 int 成员初始化的。在后一种情况下,存在编译错误(当然,不会创建大小位域之类的东西)。0
-1
-!!1
-1
评论
size_t
实际上,这是一种检查表达式 e 是否可以计算为 0 的方法,如果不能,则测试生成失败。
宏有点命名错误;它应该更像是 ,而不是 。(偶尔会有人讨论这是否是一个令人困惑的名字。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
有人问:为什么不直接使用断言
呢?
Keithmo在这里的回答有一个很好的回应:
这些宏实现编译时测试,而 assert() 是运行时测试。
完全正确。您不希望在运行时检测到内核中可能更早发现的问题!它是操作系统的关键部分。无论在编译时可以检测到什么程度的问题,都更好。
评论
static_assert
!
<
>
<=
>=
==
!=
&&
||
isdigit(c)
c
...ON_ZERO
BUG_ON
BUG_ON(foo)
foo
BUILD_BUG_ON
BUILD_BUG_ON_ZERO
(size_t)0
有些人似乎将这些宏与 混淆了。assert()
这些宏实现编译时测试,而是运行时测试。assert()
更新:
从 C11 开始,_Static_assert()
关键字可用于创建编译时测试,除非正在为旧编译器编写代码,否则应使用该关键字。
好吧,我很惊讶没有提到这种语法的替代方案。另一种常见(但较旧)的机制是调用未定义的函数,并依靠优化器来编译函数调用(如果断言正确)。
#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 功能作为扩展提供,并允许它在语句表达式等上使用。铝。
评论
so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted).
size_t
error: bit-field ‘<anonymous>’ width not an integer constant
评论
sizeof
sizeof