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

我在/usr/include/linux/kernel.h中碰到这个奇怪的macros代码:

/* 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 eg 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); })) 

什么:-!! 做?

实际上,这是一种检查expression式e是否可以被评估为0的方法,如果不是,则使构build失败

这个macros有点错误; 它应该更像BUILD_BUG_OR_ZERO ,而不是...ON_ZERO 。 ( 偶尔有人讨论这是否是一个令人困惑的名字 。)

你应该读这样的expression式:

 sizeof(struct { int: -!!(e); })) 
  1. (e) :计算expression式e

  2. !!(e) :逻辑取反两次: 0如果e == 0 ; 否则1

  3. -!!(e) :在数字上否定步骤2的expression式: 0如果是0 ; 否则-1

  4. struct{int: -!!(0);} --> struct{int: 0;} :如果它是零,那么我们声明一个具有零宽度的匿名整数位域的结构。 一切都很好,我们照常进行。

  5. struct{int: -!!(1);} --> struct{int: -1;} :另一方面,如果不是零,那么它会是一些负数。 声明宽度的任何位域是一个编译错误。

所以我们要么在结构体中有一个宽度为0的位域,这是很好的,或者是一个宽度为负的位域,这是一个编译错误。 然后我们把这个字段的sizeof ,所以我们得到一个适当的宽度的size_t (在e为零的情况下将为零)。


有人问: 为什么不使用assert

基思莫的答案在这里有一个很好的回应:

这些macros实现了一个编译时testing,而assert()是一个运行时testing。

非常正确。 您不希望在运行时检测到内核中的问题,而这些问题以前可能已经被捕获了! 这是操作系统的关键部分。 无论在什么程度上都可以在编译时检测到问题,那就更好了。

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

这只是一个让编译器在无效input上陷阱的技巧。

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

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

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

有些人似乎将这些macros与assert()混淆。

这些macros实现了一个编译时testing,而assert()是一个运行时testing。

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

 #define MY_COMPILETIME_ASSERT(test) \ do { \ extern void you_did_something_bad(void); \ if (!(test)) \ you_did_something_bad(void); \ } while (0) 

虽然这种机制起作用(只要启用了优化),但在链接之前,它有不报告错误的缺点,此时无法find函数you_did_something_bad()的定义。 这就是为什么内核开发者开始使用像负大小的位域宽度和负尺寸数组(后来停止GCC 4.4中的构build)的技巧。

为了对编译时断言的需求表示同情,GCC 4.3引入了error函数属性 ,它允许你扩展这个旧的概念,但是用你select的消息产生一个编译时错误 – 没有更多的隐藏的“消极尺寸数组“错误信息!

 #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的macros,它使用了这个特性,并且bug.h大部分macros都被相应的更新了。 不过,这个macros不能用作初始化器。 但是,使用语句expression式 (另一个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; \ }) 

这个macros只会对它的参数进行一次评估(如果它有副作用),并创build一个编译时错误,说“我告诉过你不要给我五个!” 如果expression式评估为5或不是编译时常量。

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

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

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