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]
这使得在条件中传输错误消息成为可能,如果它被触发,这很容易解释出了什么问题。
然而,这个 MSG
与 C11
的 static_assert()
有很大不同:
- 必须是一个字
- 它必须只使用标识符字符
- 不能是带双引号的字符串
这与 C11
的 static_assert()
非常不同,似乎不可能创建一个宏来在 C11
和 C90
版本之间透明切换,具体取决于在编译器上。
为了接受 "looks like C11
" 错误消息,也就是带双引号的字符串,我测试了一个新宏:
#define STATIC_ASSERT(CONDITION, MSG) \
typedef char static_assert[((void)(MSG), ((CONDITION)?1:-1))]
使用 ,
逗号运算符,此宏应接受 MSG
作为字符串,并忽略它。但是出错时会显示出来,这是本意。
它在 clang
、but 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
static_assert()
是自 C11 以来可用的非常强大的功能。
不过,对于 C11 之前的编译器,必须模拟此功能。 不难,网上有很多例子。
例如:
#define STATIC_ASSERT(CONDITION, MSG) \
typedef char static_assert_##MSG[(CONDITION)?1:-1]
这使得在条件中传输错误消息成为可能,如果它被触发,这很容易解释出了什么问题。
然而,这个 MSG
与 C11
的 static_assert()
有很大不同:
- 必须是一个字
- 它必须只使用标识符字符
- 不能是带双引号的字符串
这与 C11
的 static_assert()
非常不同,似乎不可能创建一个宏来在 C11
和 C90
版本之间透明切换,具体取决于在编译器上。
为了接受 "looks like C11
" 错误消息,也就是带双引号的字符串,我测试了一个新宏:
#define STATIC_ASSERT(CONDITION, MSG) \
typedef char static_assert[((void)(MSG), ((CONDITION)?1:-1))]
使用 ,
逗号运算符,此宏应接受 MSG
作为字符串,并忽略它。但是出错时会显示出来,这是本意。
它在 clang
、but 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