语法区别 - 宏c语言




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

如果条件为假,则创建大小为0位域,但如果条件为真/非零,则创建大小为-1-!!1 )的位域。 在前一种情况下,没有错误,并且结构用int成员初始化。 在后一种情况下,存在编译错误(当然,不会创建大小为-1位字段)。

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

什么:-!! 做?


实际上,这是一种检查表达式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()是一个运行时测试。

非常正确。 您不希望在运行时检测到您的内核中可能早期发现的问题! 这是操作系统的关键部分。 无论在编译时可以检测到什么程度的问题,都会更好。


:是一个位域。 至于!! ,这是逻辑双重否定 ,因此返回0代表假或1代表真。 而-是一个负号,即算术否定。

这只是一个窍门,让编译器在无效输入上陷入困境。

考虑BUILD_BUG_ON_ZERO 。 当-!!(e)评估为负值时,会产生编译错误。 否则-!!(e)评估为0,0宽度位域的大小为0.因此,该宏评估的值为0的size_t

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

BUILD_BUG_ON_NULL非常相似,但产生一个指针而不是int


那么,我很惊讶这个语法的替代方法没有被提及。 另一个常见(但较老)的机制是调用一个未定义的函数,并且如果断言是正确的,则依赖于优化器来编译函数调用。

#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开始,我们现在有一个名为bug.h的宏,它使用了这个特性,并且bug.h大多数宏都相应地进行了更新。 不过,这个宏不能用作初始值设定项。 但是,使用语句表达式 (另一个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或不是编译时常量。

那么为什么我们不使用这个而不是负尺寸的位域呢? 唉,目前对语句表达式的使用有许多限制,包括它们作为常量初始值设定项(对于枚举常量,位域宽度等)的使用,即使语句表达式完全是自己的常量(即可以完全评估在编译时和否则传递__builtin_constant_p()测试)。 此外,它们不能在功能体外使用。

希望GCC能很快修改这些缺点,并允许常量语句表达式用作常量初始化器。 这里面临的挑战是定义什么是合法常数表达式的语言规范。 C ++ 11为这种类型或事物添加了constexpr关键字,但在C11中不存在对应关系。 虽然C11得到了静态断言,这将解决这个问题的一部分,但它不会解决所有这些缺点。 所以我希望gcc可以通过-std = gnuc99&-std = gnuc11或者其他一些方法来扩展constexpr的功能,并允许它用在语句表达式等。 人。





linux-kernel