我很惊讶,这种语法的替代方案没有被提到。另一种常见(但较旧的)机制是调用未定义的函数,并依靠优化器在断言正确时编译掉函数调用。
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以来,我们现在有一个名为
compiletime_assert
的宏使用此功能,并且
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; \
})
这个宏将仅评估其参数一次(以防它具有副作用),并在表达式评估为五或不是编译时常量时创建一个编译时错误,该错误消息为“我告诉你不要给我五!”。
那么为什么我们不使用这个宏来代替负大小的位域呢?遗憾的是,目前对语句表达式的使用存在许多限制,包括它们作为常量初始化器的使用(对于枚举常量、位域宽度等),即使语句表达式完全是常量本身(即,在编译时可以完全评估,并且通过了
__builtin_constant_p()
测试)。此外,它们不能在函数体外使用。
希望GCC能很快改进这些缺陷,并允许常量语句表达式用作常量初始化器。挑战在于语言规范定义了什么是合法的常量表达式。C++11为此添加了constexpr关键字,但C11中没有相应的关键字。虽然C11确实获得了静态断言,可以解决部分问题,但它无法解决所有这些缺陷。因此,我希望gcc可以通过-std=gnuc99和-std=gnuc11等方式将constexpr功能作为扩展功能提供,并允许其在语句表达式等方面使用。
sizeof
的参数不会被求值。这种情况下是不是错了?如果是,为什么?因为它是一个宏吗? - Phillip Cloudsizeof
确实会“评估”类型,但不涉及值。在这种情况下无效的是类型本身。 - Winston Ewert:0
给出了一个大小为零的匿名位域,因此是一个大小为零的结构体。但这并不完全正确;在标准 C 中,:0
不是任何类型的字段声明(你不能给它一个名称),而是指令以在下一个字边界(通常是int
但不一定)上开始 下一个 位域。除非在两个(否则相邻的)位域声明之间使用,它通常没有任何意义。生成的结构体大小为零,因为它包含 没有 声明;当然这是 gcc 的扩展。 - Martin Kealey