C代码中的“:-!!”是什么?

1tu0hz3e  于 2022-12-11  发布在  其他
关注(0)|答案(5)|浏览(710)

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

:-!!的功能是什么?

cygmwpex

cygmwpex1#

实际上,这是一种检查表达式e是否可以计算为0的方法,如果不能,则使构建失败
宏的名称有些错误;它应该更像BUILD_BUG_OR_ZERO,而不是...ON_ZERO。(已经有**occasional discussions about whether this is a confusing name**。)
您应该这样阅读表达式:

sizeof(struct { int: -!!(e); }))
  1. (e):计算表达式e
  2. !!(e):逻辑取反两次:如果e == 0,则0;否则为1
  3. -!!(e):对步骤2中的表达式求反:如果是0,则为0;否则为-1
  4. struct{int: -!!(0);} --> struct{int: 0;}:如果它是零,那么我们声明一个结构体,它有一个宽度为零的匿名整数位域。一切正常,我们照常进行。
  5. struct{int: -!!(1);} --> struct{int: -1;}:另一方面,如果它 * 不是 * 零,那么它将是某个负数。声明任何具有 negative 宽度的位域都是编译错误。
    因此,我们要么得到一个宽度为0的位域,这很好,要么得到一个宽度为负的位域,这是一个编译错误。然后我们取sizeof,这样我们得到一个宽度合适的size_t(在e为0的情况下,宽度为0)。
    有的人就问:为什么不直接使用assert
    keithmo's answer在这里有一个很好的响应:
    这些宏实现编译时测试,而assert()是运行时测试。
    完全正确。你不想在运行时检测到你的 kernel 中的问题,这些问题本可以更早地被发现!它是操作系统的一个关键部分。无论在编译时检测到什么程度的问题,那都是更好的。
7gcisfzg

7gcisfzg2#

:是位字段,对于!!,即logical double negation,因此如果为假,则返回0,如果为真,则返回1,而-是负号,即算术非。
这只是让编译器在无效输入时呕吐的一个技巧。
BUILD_BUG_ON_ZERO为例。当-!!(e)的计算结果为负值时,将产生编译错误。否则,-!!(e)的计算结果为0,宽度为0的位域的大小为0。因此,宏的计算结果为值为0的size_t
在我看来,这个名字很弱,因为当输入为 * 而不是 * 0时,构建实际上失败了。
BUILD_BUG_ON_NULL非常类似,但产生的是指标,而不是int

nle07wnf

nle07wnf3#

有些人似乎将这些宏与assert()混淆了。
这些宏实现编译时测试,而assert()是运行时测试。

iyfjxgzm

iyfjxgzm4#

另一种常见的(但较旧的)机制是调用一个未定义的函数,如果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()的定义。这就是为什么内核开发人员开始使用负大小的位域宽度和负大小的数组(后者在愚者4.4中停止了破坏构建)等技巧。
为了满足编译时Assert的需要,愚者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 * 语句表达式 *(另一个愚者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()测试)。此外,它们不能在函数体之外使用。
希望愚者能尽快修正这些缺点,允许常量语句表达式作为常量初始化器使用。这里的挑战是语言规范定义什么是法律的的常量表达式。C++11添加了constexpr关键字,但在C11中没有对应的关键字。虽然C11确实得到了静态Assert,这将解决这个问题的一部分,所以我希望gcc可以通过-std= gnuc 99 & -std= gnuc 11或其他类似的方式将constexpr功能作为扩展提供,并允许它用于语句表达式等。

ztmd8pv5

ztmd8pv55#

如果条件为false,它会创建一个大小为0的位域;如果条件为true/非零,它会创建一个大小为-1-!!1)的位域。在前一种情况下,没有错误,结构体初始化为int成员。在后一种情况下,会出现编译错误(当然,不会创建大小为-1的位域)。

相关问题