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

2024-10-18 09:00:00
admin
原创
72
摘要:问题描述:我碰到了这个奇怪的宏代码/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), ...

问题描述:

我碰到了这个奇怪的宏代码/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); }))
  1. (e):计算表达式e

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

完全正确。您不想在运行时检测内核中的问题,因为这些问题本来可以早点发现!内核是操作系统的关键部分。无论在编译时能够检测到多少问题,这都是最好的。

解决方案 2:

:位域。至于!!,它是逻辑双重否定,因此0为假时返回 ,1为真时返回 。而-是减号,即算术否定。

这只是让编译器对无效输入做出反应的一个技巧。

考虑一下BUILD_BUG_ON_ZERO。当-!!(e)计算结果为负值时,会产生编译错误。否则-!!(e)计算结果为 0,而 0 宽度的位域的大小为 0。因此,宏计算结果为值为size_t0。

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

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`

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用