什么是':-!!'在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
I bumped into this strange macro code in /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); }))
What does :-!!
do?
Update: In recent times, the macro has been moved to /usr/include/linux/build_bug.h
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
实际上,这是一种检查表达式 e 是否可以计算为 0 的方法,如果不是,则构建失败。
该宏的命名有些错误;它应该更像是
BUILD_BUG_OR_ZERO
,而不是...ON_ZERO
。 (偶尔会讨论这是否是一个令人困惑的名称< /a>。)您应该这样阅读表达式:
(e)
:计算表达式e
。!!(e)
:逻辑取反两次:0
ife == 0
;否则1
.-!!(e)
:如果是0
,则对步骤 2 中的表达式进行数值否定:0
;否则-1
.struct{int: -!!(0);} --> struct{int: 0;}
:如果它为零,则我们声明一个具有宽度为零的匿名整数位字段的结构。一切都很好,我们照常进行。struct{int: -!!(1);} --> struct{int: -1;}
:另一方面,如果它不为零,那么它将是某个负数。声明任何宽度为负的位域都是编译错误。因此,我们要么得到一个结构体中宽度为 0 的位域(这很好),要么得到一个负宽度的位域(这是一个编译错误)。然后我们采用该字段的 sizeof ,这样我们就得到了具有适当宽度的 size_t (在 e 为零的情况下,该宽度为零) 。
有些人问:为什么不直接使用
assert
?keithmo 的回答 这里有一个很好的回应:
完全正确。您不想在运行时检测内核中本来可以更早发现的问题!它是操作系统的关键部分。无论何种程度的问题都可以在编译时检测到,那就更好了。
This is, in effect, a way to check whether the expression e can be evaluated to be 0, and if not, to fail the build.
The macro is somewhat misnamed; it should be something more like
BUILD_BUG_OR_ZERO
, rather than...ON_ZERO
. (There have been occasional discussions about whether this is a confusing name.)You should read the expression like this:
(e)
: Compute expressione
.!!(e)
: Logically negate twice:0
ife == 0
; otherwise1
.-!!(e)
: Numerically negate the expression from step 2:0
if it was0
; otherwise-1
.struct{int: -!!(0);} --> struct{int: 0;}
: If it was zero, then we declare a struct with an anonymous integer bitfield that has width zero. Everything is fine and we proceed as normal.struct{int: -!!(1);} --> struct{int: -1;}
: On the other hand, if it isn't zero, then it will be some negative number. Declaring any bitfield with negative width is a compilation error.So we'll either wind up with a bitfield that has width 0 in a struct, which is fine, or a bitfield with negative width, which is a compilation error. Then we take
sizeof
that field, so we get asize_t
with the appropriate width (which will be zero in the case wheree
is zero).Some people have asked: Why not just use an
assert
?keithmo's answer here has a good response:
Exactly right. You don't want to detect problems in your kernel at runtime that could have been caught earlier! It's a critical piece of the operating system. To whatever extent problems can be detected at compile time, so much the better.
:
是一个位字段。至于!!
,即 逻辑双重否定 和因此,返回0
(表示 false)或1
(表示 true)。其中-
是一个减号,即算术负数。这只是让编译器因无效输入而报错的一个技巧。
考虑
BUILD_BUG_ON_ZERO
。当-!!(e)
计算结果为负值时,会产生编译错误。否则,-!!(e)
的计算结果为 0,并且 0 宽度的位域的大小为 0。因此,该宏的计算结果为值为 0 的size_t
。名称为我认为很弱,因为当输入不为零时,构建实际上会失败。
BUILD_BUG_ON_NULL
非常相似,但生成一个指针而不是int
。The
:
is a bitfield. As for!!
, that is logical double negation and so returns0
for false or1
for true. And the-
is a minus sign, i.e. arithmetic negation.It's all just a trick to get the compiler to barf on invalid inputs.
Consider
BUILD_BUG_ON_ZERO
. When-!!(e)
evaluates to a negative value, that produces a compile error. Otherwise-!!(e)
evaluates to 0, and a 0 width bitfield has size of 0. And hence the macro evaluates to asize_t
with value 0.The name is weak in my view because the build in fact fails when the input is not zero.
BUILD_BUG_ON_NULL
is very similar, but yields a pointer rather than anint
.有些人似乎将这些宏与
assert()
混淆了。这些宏实现编译时测试,而
assert()
是运行时测试。更新:
从 C11 开始,
_Static_assert()
关键字可用于创建编译时测试,除非是为旧编译器编写代码,否则应该使用它。Some people seem to be confusing these macros with
assert()
.These macros implement a compile-time test, while
assert()
is a runtime test.Update:
As of C11, the
_Static_assert()
keyword is available to create compile time tests, and should be used unless code is being written for old compilers.嗯,我很惊讶没有提到这种语法的替代方案。另一种常见(但较旧)的机制是调用未定义的函数,并依赖优化器在断言正确时编译函数调用。
虽然此机制有效(只要启用了优化),但它的缺点是在链接之前不会报告错误,此时它无法找到函数 you_did_something_bad() 的定义。这就是为什么内核开发人员开始使用负大小位字段宽度和负大小数组等技巧(后者在 GCC 4.4 中停止破坏构建)。
为了满足编译时断言的需要,GCC 4.3 引入了
错误
函数属性这允许您扩展这个旧的概念,但会生成一个编译时错误并显示您选择的消息——不再有神秘的“负大小数组”错误消息!事实上,从 Linux 3.9 开始,我们现在有一个名为
compiletime_assert
使用此功能以及bug.h
已相应更新。不过,该宏不能用作初始值设定项。但是,使用 语句表达式(另一个 GCC C 扩展),你可以!该宏将对其参数进行一次评估(以防它有副作用),并创建一个编译时错误,提示“我告诉过你不要给我五分!”如果表达式的计算结果为 5 或者不是编译时常量。
那么为什么我们不使用它而不是负大小的位字段呢?遗憾的是,目前语句表达式的使用存在许多限制,包括将它们用作常量初始值设定项(对于枚举常量、位域宽度等),即使语句表达式自身完全是常量(即可以完全求值)在编译时,否则传递
__builtin_constant_p()
测试)。此外,它们不能在函数体之外使用。希望 GCC 能够尽快修正这些缺点,并允许使用常量语句表达式作为常量初始值设定项。这里的挑战是定义什么是合法常量表达式的语言规范。 C++11 仅针对此类型或事物添加了 constexpr 关键字,但 C11 中不存在对应项。虽然 C11 确实获得了静态断言,这将解决部分问题,但它并不能解决所有这些缺点。所以我希望 gcc 可以通过 -std=gnuc99 & 提供 constexpr 功能作为扩展。 -std=gnuc11 或类似的东西,并允许其在语句表达式等上使用。等人。
Well, I am quite surprised that the alternatives to this syntax have not been mentioned. Another common (but older) mechanism is to call a function that isn't defined and rely on the optimizer to compile-out the function call if your assertion is correct.
While this mechanism works (as long as optimizations are enabled) it has the downside of not reporting an error until you link, at which time it fails to find the definition for the function you_did_something_bad(). That's why kernel developers starting using tricks like the negative sized bit-field widths and the negative-sized arrays (the later of which stopped breaking builds in GCC 4.4).
In sympathy for the need for compile-time assertions, GCC 4.3 introduced the
error
function attribute that allows you to extend upon this older concept, but generate a compile-time error with a message of your choosing -- no more cryptic "negative sized array" error messages!In fact, as of Linux 3.9, we now have a macro called
compiletime_assert
which uses this feature and most of the macros inbug.h
have been updated accordingly. Still, this macro can't be used as an initializer. However, using by statement expressions (another GCC C-extension), you can!This macro will evaluate its parameter exactly once (in case it has side-effects) and create a compile-time error that says "I told you not to give me a five!" if the expression evaluates to five or is not a compile-time constant.
So why aren't we using this instead of negative-sized bit-fields? Alas, there are currently many restrictions of the use of statement expressions, including their use as constant initializers (for enum constants, bit-field width, etc.) even if the statement expression is completely constant its self (i.e., can be fully evaluated at compile-time and otherwise passes the
__builtin_constant_p()
test). Further, they cannot be used outside of a function body.Hopefully, GCC will amend these shortcomings soon and allow constant statement expressions to be used as constant initializers. The challenge here is the language specification defining what is a legal constant expression. C++11 added the constexpr keyword for just this type or thing, but no counterpart exists in C11. While C11 did get static assertions, which will solve part of this problem, it wont solve all of these shortcomings. So I hope that gcc can make a constexpr functionality available as an extension via -std=gnuc99 & -std=gnuc11 or some such and allow its use on statement expressions et. al.
如果条件为假,它将创建一个大小
0
位字段,但如果条件为 true,则创建一个大小-1
(-!!1
) 位字段/非零。在前一种情况下,没有错误,结构体是用 int 成员初始化的。在后一种情况下,会出现编译错误(当然,不会创建大小-1
位字段之类的东西)。It's creating a size
0
bitfield if the condition is false, but a size-1
(-!!1
) bitfield if the condition is true/non-zero. In the former case, there is no error and the struct is initialized with an int member. In the latter case, there is a compile error (and no such thing as a size-1
bitfield is created, of course).