gcc 上 C90 的静态断言

static assert for C90 on gcc

static_assert() 是自 C11 以来可用的非常强大的功能。

不过,对于 C11 之前的编译器,必须模拟此功能。 不难,网上有很多例子。

例如:

#define STATIC_ASSERT(CONDITION, MSG) \
typedef char static_assert_##MSG[(CONDITION)?1:-1]

这使得在条件中传输错误消息成为可能,如果它被触发,这很容易解释出了什么问题。

然而,这个 MSGC11static_assert() 有很大不同:

这与 C11static_assert() 非常不同,似乎不可能创建一个宏来在 C11C90 版本之间透明切换,具体取决于在编译器上。

为了接受 "looks like C11" 错误消息,也就是带双引号的字符串,我测试了一个新宏:

#define STATIC_ASSERT(CONDITION, MSG) \
typedef char static_assert[((void)(MSG), ((CONDITION)?1:-1))]

使用 , 逗号运算符,此宏应接受 MSG 作为字符串,并忽略它。但是出错时会显示出来,这是本意。

它在 clangbut not of gcc 上运行良好:error: variably modified at file scope

我正在尝试了解原因,以及是否有解决方法

如果您将 typedef-array-trick 替换为 enum-trick,那么您将得到似乎同时适用于 clang 和 gcc 的东西:

#define CONDITION 1

#define TOKENPASTE(a, b) a ## b // "##" is the "Token Pasting Operator"
#define TOKENPASTE2(a,b) TOKENPASTE(a, b) // expand then paste
#define static_assert(x, msg) enum { TOKENPASTE2(ASSERT_line_,__LINE__) \
    = 1 / (msg && (x)) }

static_assert( CONDITION, "This should pass");
static_assert(!CONDITION, "This should fail");

这给了我,以 gcc 为例,在 foo.c 的第 9 行:

foo.c:9: warning: division by zero [-Wdiv-by-zero]
 static_assert(!CONDITION, "This should fail");
 ^
foo.c:9: error: enumerator value for 'ASSERT_line_9' is not an integer constant
 static_assert(!CONDITION, "This should fail");
 ^~~~~~~~~~~~~

(这里使用了 gcc 开关 -ftrack-macro-expansion=0,因为额外的错误消息没有多大帮助,只会增加噪音。)

请注意,仍然需要对名称进行一些修改,但您省略了。这里文本 ASSERT_line_ 与变量 __LINE__ 组合在一起。这确保了唯一的名称,前提是:

  • 不要在一行中使用两次。
  • 您不要在头文件中使用它(或者相信运气)。
  • 您的代码不会碰巧在其他地方使用像 ASSERT_line_9 这样的标识符。

对于头文件,您需要在某处添加一个仅包含标识符字符的单词。例如:

#define static_assert3(x, msg, file) enum { TOKENPASTE2(file,__LINE__) = \
    1 / (msg && (x)) }
#define static_assert(x, msg) static_assert3(x, msg, my_header_h_)

如果第 17 行失败,gcc 将给出如下错误:

 error: enumerator value for 'my_header_h_17' is not an integer constant

在头文件中进行重整的另一种方法是将 __LINE__ 替换为 __COUNTER__。我没有使用它,因为它是非标准的,而且因为 clang 采纳它的速度很慢。但现在它已经在 gcc、msvc 和 clang 中存在了大约五年。


您可以尝试对您的 typedef-array 想法进行相同的修改,并将逗号运算符替换为 &&。然后你的 gcc 错误变成警告。例如,将您的 godbolt 示例修改为:

typedef char static_assert_2["hello world!" && (CONDITION) ? 1 : -1];

为 gcc 提供不需要的 warning: variably modified 'static_assert_2' at file scope

One-liner

#define STATIC_ASSERT(CONDITION, MSG) { typedef char test[(CONDITION)?1:-1]; (void)(test*) #MSG; } (void)0