C 语言中的 ':-!!' 是什么?
- 2024-10-18 09:00:00
- admin 原创
- 73
问题描述:
我碰到了这个奇怪的宏代码/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
解决方案 1:
实际上,这是一种检查表达式 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
)。
有些人问:为什么不直接使用assert
?
keithmo 的回答在这里有一个很好的回应:
这些宏实现编译时测试,而 assert() 是运行时测试。
完全正确。您不想在运行时检测内核中的问题,因为这些问题本来可以早点发现!内核是操作系统的关键部分。无论在编译时能够检测到多少问题,这都是最好的。
解决方案 2:
是:
位域。至于!!
,它是逻辑双重否定,因此0
为假时返回 ,1
为真时返回 。而-
是减号,即算术否定。
这只是让编译器对无效输入做出反应的一个技巧。
考虑一下BUILD_BUG_ON_ZERO
。当-!!(e)
计算结果为负值时,会产生编译错误。否则-!!(e)
计算结果为 0,而 0 宽度的位域的大小为 0。因此,宏计算结果为值为size_t
0。
在我看来,这个名字很弱,因为当输入不为零时,构建实际上会失败。
BUILD_BUG_ON_NULL
非常相似,但是产生的是一个指针而不是int
。
解决方案 3:
有些人似乎将这些宏与混淆了assert()
。
这些宏实现了编译时测试,而assert()
是运行时测试。
更新:
从 C11 开始,该_Static_assert()
关键字可用于创建编译时测试,除非为旧编译器编写代码,否则应该使用它。
解决方案 4:
嗯,我很惊讶没有提到这种语法的替代方案。另一种常见(但较旧)的机制是调用未定义的函数,并依靠优化器在断言正确的情况下编译出函数调用。
#define MY_COMPILETIME_ASSERT(test) \n do { \n extern void you_did_something_bad(void); \n if (!(test)) \n you_did_something_bad(void); \n } while (0)
虽然此机制有效(只要启用了优化),但它的缺点是,直到您链接时才会报告错误,而链接时它无法找到函数 you_did_something_bad() 的定义。这就是为什么内核开发人员开始使用诸如负数大小的位字段宽度和负数大小的数组之类的技巧(后者在 GCC 4.4 中停止破坏构建)。
出于对编译时断言需求的理解,GCC 4.3 引入了error
函数属性,允许您扩展这个旧概念,但会生成一个带有您选择的消息的编译时错误 - 不再出现神秘的“负大小数组”错误消息!
#define MAKE_SURE_THIS_IS_FIVE(number) \n do { \n extern void this_isnt_five(void) __attribute__((error( \n "I asked for five and you gave me " #number))); \n if ((number) != 5) \n this_isnt_five(); \n } while (0)
事实上,从 Linux 3.9 开始,我们现在有一个名为的宏compiletime_assert
使用此功能,并且中的大多数宏bug.h
都已进行了相应更新。不过,这个宏不能用作初始化程序。但是,使用语句表达式(另一个 GCC C 扩展),你可以!
#define ANY_NUMBER_BUT_FIVE(number) \n ({ \n typeof(number) n = (number); \n extern void this_number_is_five(void) __attribute__(( \n error("I told you not to give me a five!"))); \n if (n == 5) \n this_number_is_five(); \n n; \n })
这个宏将只对其参数求值一次(以防它有副作用),并且如果表达式求值为五或者不是编译时常量,则会创建一个编译时错误,提示“我告诉过你不要给我五分!”。
那么,为什么我们不使用它来代替负数大小的位字段呢?唉,目前语句表达式的使用有很多限制,包括将其用作常量初始化器(用于枚举常量、位字段宽度等),即使语句表达式本身完全是常量(即,可以在编译时完全求值并通过测试__builtin_constant_p()
)。此外,它们不能在函数体之外使用。
希望 GCC 能尽快修正这些缺陷,并允许将常量语句表达式用作常量初始化器。这里的挑战是语言规范定义什么是合法的常量表达式。C++11 为这种类型或事物添加了 constexpr 关键字,但 C11 中没有对应的关键字。虽然 C11 确实获得了静态断言,这将解决部分问题,但它不会解决所有这些缺陷。所以我希望 gcc 可以通过 -std=gnuc99 & -std=gnuc11 或类似方式将 constexpr 功能作为扩展提供,并允许将其用于语句表达式等。
解决方案 5:
0
如果条件为假,则创建一个大小位域,如果条件为真/非零,则创建一个大小-1
( ) 位域。在前一种情况下,没有错误,并且结构体用 int 成员初始化。在后一种情况下,会出现编译错误(当然,不会创建大小位域)。-!!1
`-1`
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件