如何在C语言中(而非C++)实现编译时静态断言,尤其是在GCC编译器下的实现?
如何在C语言中(而非C++)实现编译时静态断言,尤其是在GCC编译器下的实现?
C11标准添加了_Static_assert
关键字。
这是自gcc-4.6以来实现的:
_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */
第一个占位符需要是一个整型常量表达式。第二个占位符是一个常量字符串字面量,可以很长(_Static_assert(0, L"assertion of doom!")
)。
需要注意的是最近版本的clang也实现了这一功能。
error: expected declaration specifiers or '...' before 'sizeof'
的错误:static_assert( sizeof(int) == sizeof(long int), "Error!);
(顺便说一下,我正在使用的是 C 而不是 C++)。 - user10607_Static_assert(sizeof(int) == sizeof(long int),"错误!");
在我的机器上出现错误。 - emsrerror: expected declaration specifiers or '...' before 'sizeof'
和error: expected declaration specifiers or '...' before string constant
(他指的是"Error!"
字符串)(还要注意:我正在使用-std=c11进行编译。将声明放在函数内部时,一切正常(预期成功或失败)。) - user10607_Static_assert
而不是类似于C ++的static_assert
。您需要#include <assert.h>
来获取static_assert宏。 - emsr这在函数和非函数作用域中都有效(但不适用于结构体和联合体内部)。
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]
STATIC_ASSERT(1,this_should_be_true);
int main()
{
STATIC_ASSERT(1,this_should_be_true);
}
如果编译时断言无法匹配,则GCC将生成一个几乎可以理解的错误信息:sas.c:4: error: size of array 'static_assertion_this_should_be_true' is negative
宏应该被更改为为typedef生成唯一的名称(即在static_assert_...
名称的结尾连接__LINE__
)
可以使用这个代替三元运算符:#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1]
,它甚至可以在陈旧的cc65(针对6502 CPU)编译器上工作。
更新:为了完整起见,以下是带有__LINE__
的版本
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1]
// token pasting madness:
#define COMPILE_TIME_ASSERT3(X,L) STATIC_ASSERT(X,static_assertion_at_line_##L)
#define COMPILE_TIME_ASSERT2(X,L) COMPILE_TIME_ASSERT3(X,L)
#define COMPILE_TIME_ASSERT(X) COMPILE_TIME_ASSERT2(X,__LINE__)
COMPILE_TIME_ASSERT(sizeof(long)==8);
int main()
{
COMPILE_TIME_ASSERT(sizeof(int)==4);
}
更新2: GCC 特定代码
GCC 4.3(我猜)引入了“error”和“warning”函数属性。如果带有该属性的函数调用不能通过死代码消除(或其他措施)而被消除,则会生成错误或警告。这可用于使用用户定义的失败描述进行编译时断言。现在需要确定如何在命名空间范围内使用它们,而不必借助虚拟函数:
#define CTC(X) ({ extern int __attribute__((error("assertion failure: '" #X "' not true"))) compile_time_check(); ((X)?0:compile_time_check()),0; })
// never to be called.
static void my_constraints()
{
CTC(sizeof(long)==8);
CTC(sizeof(int)==4);
}
int main()
{
}
这是它的外观:
$ gcc-mp-4.5 -m32 sas.c
sas.c: In function 'myc':
sas.c:7:1: error: call to 'compile_time_check' declared with attribute error: assertion failure: `sizeof(int)==4` not true
-Og
)通常足以使其工作,并且不应干扰调试。如果未定义__OPTIMIZE__
(和__GNUC__
),则可以考虑将静态断言设置为无操作或运行时断言。 - Søren Løvborgn
是变量,则此操作不会报错:STATIC_ASSERT(n == 42, always_succeeds)
。在函数内部,这将定义一个变量数组,这是可以的。但是,如果n
是一个编译时常量且与42不同,则此操作将失败。非常令人困惑。 - MarcHSTATIC_ASSERT(test_for_true)
宏,适用于任何版本的C或C++。以下是我的三个主要解决方案:
[最简单的选择!] STATIC_ASSERT(test_for_true)
仅适用于 C11 或更高版本以及 C++11 或更高版本:
#include <assert.h>
#define STATIC_ASSERT(test_for_true) \
static_assert((test_for_true), "(" #test_for_true ") 失败")
或者 [我的首选],STATIC_ASSERT(test_for_true)
适用于 任何 版本的 C(作为 gcc 编译器的扩展),包括 C90、C99、C11、C17 等,以及 C++11 或更高版本(无法处理旧版本的 C++):
#ifdef __cplusplus
#ifndef _Static_assert
#define _Static_assert static_assert
#endif
#endif
#define STATIC_ASSERT(test_for_true) \
_Static_assert((test_for_true), "(" #test_for_true ") 失败")
STATIC_ASSERT(test_for_true)
适用于 任何 版本的 C 和 C++:
如果你想要一个单一的 STATIC_ASSERT
宏在 所有 版本的 C 和 C++ 中都能工作,我在下面的部分中提供了它。你可以在底部的 "测试摘要" 部分中看到我用来测试它的构建命令和语言设置。使静态断言在 pre-C++11 中工作,比如 C++98、C++03 等,是困难的部分!下面的 _Static_assert_hack
宏就是处理早期版本 C++ 的部分。以下是处理所有版本的 C 和 C++ 的完整代码块,为了方便起见,大部分注释已被删除:
// 参考:https://dev59.com/qXA75IYBdhLWcg3wRWyc#54993033
#define CONCAT_(prefix, suffix) prefix##suffix
#define CONCAT(prefix, suffix) CONCAT_(prefix, suffix)
#define MAKE_UNIQUE_VARIABLE_NAME(prefix) CONCAT(prefix##_, __LINE__)
/* 需要用于 **pre-C++11** 的静态断言 hack,如 C++98、C++03 等。
* - 它只适用于 C++,不适用于 C!
*/
#define _Static_assert_hack(expression, message) \
struct MAKE_UNIQUE_VARIABLE_NAME(static_assertion_failed) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wunused-local-typedefs\"") \
typedef char static_assertion_failed[(expression) ? 1 : -1]; \
_Pragma("GCC diagnostic pop") \
}
/* 仅适用于 C++:
* 参考:https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html
*/
#ifdef __cplusplus
#if __cplusplus < 201103L
/* 适用于 pre-C++11 */
#ifndef _Static_assert
#define _Static_assert _Static_assert_hack
#endif
#else
/* 适用于 C++11 或更高版本 */
#ifndef _Static_assert
#define _Static_assert static_assert
#endif
#endif
#endif
/* 适用于 C **和** C++: */
#define STATIC_ASSERT(test_for_true) \
_Static_assert((test_for_true), "(" #test_for_true ") 失败")
_Static_assert(expression, message)
在C11或更高版本中可用。
根据上面的cppreference.com社区wiki链接,static_assert
也作为一个方便的宏,以匹配C++11中的命名,在头文件<assert.h>
中也可用。因此,要在C11或更高版本中获得类似C++的static_assert
作为宏,您还应该#include <assert.h>
。
_Static_assert(expression)
(即没有message
部分)也可在C23或更高版本中使用。static_assert(expression, message)
在C++11或更高版本中可用。
static_assert(expression)
(即没有message
部分)也可在C++17或更高版本中使用。_Static_assert
作为gcc扩展支持所有版本的C,包括c90、c99、c11、c17等。static_assert
作为宏也可用于C11或更高版本,如果您还#include <assert.h>
。static_assert
作为C++11或更高版本的关键字被支持。在C++中,您不需要像在C中那样#include <assert.h>
来使用这种格式。更早的平台根本不支持static_assert
或_Static_assert
。例如,4.6之前的GCC版本不支持_Static_assert
,4.3之前的G++版本不支持static_assert
,这是由C11和C++11标准化的。
C的_Static_assert
和C++的static_assert
是可以在不包括<assert.h>
的情况下使用的关键字。Gnulib的替代方法是需要包括<assert.h>
的宏。
STATIC_ASSERT
的包装宏,将参数简化为1个,并自动产生message
参数,这样我就可以使用STATIC_ASSERT(expression)
而不是STATIC_ASSERT(expression, message)
。以下是如何轻松实现这一点的方法:在我的static_assert_for_all_versions_of_c_and_cpp.c中测试上述代码片段。
对于gcc的pre-C11,gcc已经定义了_Static_assert(expression, message)
,这真的很好。所以,只需使用它并完成上述操作!但是,如果你没有使用gcc编译器呢?你能做些什么呢?
嗯,我注意到了一些非常有趣的事情。如果我在C90中使用_Static_assert(1 > 2, "this should fail");
,并使用以下构建命令:
gcc -Wall -Wextra -Werror -O3 -std=c90 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
In file included from /usr/include/features.h:461,
from /usr/include/x86_64-linux-gnu/bits/libc-header-start.h:33,
from /usr/include/stdint.h:26,
from /usr/lib/gcc/x86_64-linux-gnu/9/include/stdint.h:9,
from static_assert_for_all_versions_of_c_and_cpp.c:73:
static_assert_for_all_versions_of_c_and_cpp.c: In function ‘main’:
static_assert_for_all_versions_of_c_and_cpp.c:224:5: error: negative width in bit-field ‘__error_if_negative’
224 | _Static_assert(1 > 2, "this should fail");
| ^~~~~~~~~~~~~~
__error_if_negative
,我只能在一个位置找到结果,即这里:https://github.com/gcc-mirror/gcc/blob/master/libgcc/soft-fp/soft-fp.h#L69-L71。# define _FP_STATIC_ASSERT(expr, msg) \
extern int (*__Static_assert_function (void)) \
[!!sizeof (struct { int __error_if_negative: (expr) ? 2 : -1; })]
_FP_STATIC_ASSERT
替换为_Static_assert
,就像这样:# define _Static_assert(expr, msg) \
extern int (*__Static_assert_function (void)) \
[!!sizeof (struct { int __error_if_negative: (expr) ? 2 : -1; })]
_Static_assert
技巧的注意事项:static_assert
一样在结构体和联合体中工作!这就是它。你可以在这里测试它,我的static_assert_for_all_versions_of_c_and_cpp.c:#define CONCAT_(prefix, suffix) prefix##suffix
#define CONCAT(prefix, suffix) CONCAT_(prefix, suffix)
#define MAKE_UNIQUE_VARIABLE_NAME(prefix) CONCAT(prefix##_, __LINE__)
/* Static assert hack required for **pre-C++11**, such as C++98, C++03, etc. */
/* - It works only with C++, NOT with C! */
#define _Static_assert_hack(expression, message) \
struct MAKE_UNIQUE_VARIABLE_NAME(static_assertion_failed) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wunused-local-typedefs\"") \
typedef char static_assertion_failed[(expression) ? 1 : -1]; \
_Pragma("GCC diagnostic pop") \
}
STATIC_ASSERT()
,在使用gcc编译时有效只需稍作修改,改变使用的风格和时间,下面的代码也可以在非gcc编译器上适用于任何版本的C和C++。
按照目前的编写方式,我预计它可以在所有版本的C和gnu C以及所有版本的C++和gnu++上使用,无论是使用gcc/g++编译器还是LLVM clang编译器。
这是最终版本:一个静态断言来处理任何版本的C或C++!
/* --------------------------------- START ---------------------------------- */
/* OR [BEST], for **any version of C OR C++**: */
/* See: https://dev59.com/eHI-5IYBdhLWcg3w9tdw#71899854 */
#define CONCAT_(prefix, suffix) prefix##suffix
/* Concatenate `prefix, suffix` into `prefixsuffix` */
#define CONCAT(prefix, suffix) CONCAT_(prefix, suffix)
/* Make a unique variable name containing the line number at the end of the */
/* name. Ex: `uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0;` would */
/* produce `uint64_t counter_7 = 0` if the call is on line 7! */
#define MAKE_UNIQUE_VARIABLE_NAME(prefix) CONCAT(prefix##_, __LINE__)
/* Static assert hack required for **pre-C++11**, such as C++98, C++03, etc. */
/* - It works only with C++, NOT with C! */
/* See: */
/* 1. [my ans with this] https://dev59.com/qXA75IYBdhLWcg3wRWyc#54993033 */
/* 1. Info. on `_Pragma()`: https://dev59.com/43A75IYBdhLWcg3wSm64#47518775 */
/* 1. The inspiration for this `typedef char` array hack as a struct */
/* definition: https://dev59.com/qXA75IYBdhLWcg3wRWyc#3385694 */
/* Discard the `message` portion entirely. */
#define _Static_assert_hack(expression, message) \
struct MAKE_UNIQUE_VARIABLE_NAME(static_assertion_failed) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wunused-local-typedefs\"") \
typedef char static_assertion_failed[(expression) ? 1 : -1]; \
_Pragma("GCC diagnostic pop") \
}
/* For C++ only: */
/* See: https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html */
#ifdef __cplusplus
#if __cplusplus < 201103L
/* for pre-C++11 */
#ifndef _Static_assert
#define _Static_assert _Static_assert_hack
#endif
#else
/* for C++11 or later */
#ifndef _Static_assert
#define _Static_assert static_assert
#endif
#endif
#endif
/* For C **and** C++: */
#define STATIC_ASSERT(test_for_true) \
_Static_assert((test_for_true), "(" #test_for_true ") failed")
/* ---------------------------------- END ----------------------------------- */
_Pragma()
的信息:如何在几行代码中禁用GCC警告typedef char
数组hack作为结构定义的灵感来源:
STATIC_ASSERT
中,使用在C++11之前的情况下,这里有一些样例代码,其中包含一个预期会失败的静态断言,因为它是错误的。typedef union data_u
{
data_t data;
uint8_t bytes[sizeof(data_t)];
STATIC_ASSERT(2 > 2); /* this should fail */
} data_union_t;
_Static_assert
的方式也不好:eRCaGuy_hello_world/c$ g++ -Wall -Wextra -Werror -O3 -std=c++03 static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
static_assert_for_all_versions_of_c_and_cpp.c:129:67: error: narrowing conversion of ‘-1’ from ‘int’ to ‘long unsigned int’ is ill-formed in C++11 [-Werror=narrowing]
129 | typedef char static_assertion_failed[(expression) ? 1 : -1]; \
| ^
static_assert_for_all_versions_of_c_and_cpp.c:139:36: note: in expansion of macro ‘_Static_assert_hack’
139 | #define _Static_assert _Static_assert_hack
| ^~~~~~~~~~~~~~~~~~~
static_assert_for_all_versions_of_c_and_cpp.c:151:5: note: in expansion of macro ‘_Static_assert’
151 | _Static_assert((test_for_true), "(" #test_for_true ") failed")
| ^~~~~~~~~~~~~~
static_assert_for_all_versions_of_c_and_cpp.c:187:5: note: in expansion of macro ‘STATIC_ASSERT’
187 | STATIC_ASSERT(2 > 2);
| ^~~~~~~~~~~~~
static_assert_for_all_versions_of_c_and_cpp.c:129:59: error: size ‘-1’ of array ‘static_assertion_failed’ is negative
129 | typedef char static_assertion_failed[(expression) ? 1 : -1]; \
| ~~~~~~~~~~~~~^~~~~~~~
static_assert_for_all_versions_of_c_and_cpp.c:139:36: note: in expansion of macro ‘_Static_assert_hack’
139 | #define _Static_assert _Static_assert_hack
| ^~~~~~~~~~~~~~~~~~~
static_assert_for_all_versions_of_c_and_cpp.c:151:5: note: in expansion of macro ‘_Static_assert’
151 | _Static_assert((test_for_true), "(" #test_for_true ") failed")
| ^~~~~~~~~~~~~~
static_assert_for_all_versions_of_c_and_cpp.c:187:5: note: in expansion of macro ‘STATIC_ASSERT’
187 | STATIC_ASSERT(2 > 2);
| ^~~~~~~~~~~~~
cc1plus: all warnings being treated as errors
请参阅static_assert_for_all_versions_of_c_and_cpp.c。
最终的STATIC_ASSERT(test_for_true)
宏位于上方,它适用于所有版本的C和C++,在Linux Ubuntu 20.04上使用gcc编译器版本(gcc --version
)9.4.0
(gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
)进行了测试。
STATIC_ASSERT()
宏的是-std=c90
和-std=c99
!其他所有选项都支持在任何地方使用STATIC_ASSERT
,包括函数外部、函数内部以及结构体和联合体内部。# -------------------
# 1. In C:
# -------------------
gcc -Wall -Wextra -Werror -O3 -std=c90 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
gcc -Wall -Wextra -Werror -O3 -std=c99 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
gcc -Wall -Wextra -Werror -O3 -std=c11 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
gcc -Wall -Wextra -Werror -O3 -std=c17 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
# gnu C
gcc -Wall -Wextra -Werror -O3 -std=gnu90 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
gcc -Wall -Wextra -Werror -O3 -std=gnu99 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
gcc -Wall -Wextra -Werror -O3 -std=gnu11 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
# [my default C build cmd I use today]:
gcc -Wall -Wextra -Werror -O3 -std=gnu17 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
# -------------------
# 2. In C++
# -------------------
g++ -Wall -Wextra -Werror -O3 -std=c++98 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
g++ -Wall -Wextra -Werror -O3 -std=c++03 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
g++ -Wall -Wextra -Werror -O3 -std=c++11 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
g++ -Wall -Wextra -Werror -O3 -std=c++17 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
# gnu++
g++ -Wall -Wextra -Werror -O3 -std=gnu++98 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
g++ -Wall -Wextra -Werror -O3 -std=gnu++03 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
g++ -Wall -Wextra -Werror -O3 -std=gnu++11 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
# [my default C++ build cmd I use today]:
g++ -Wall -Wextra -Werror -O3 -std=gnu++17 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
BUILD_BUG_ON()
替代。在“适用于所有版本的C和C++”情况下,它是否能够取代或更好地工作?assert.h
中有一个static_assert
宏时? - Kami Kazestatic_assert
自C11起在C中定义。它是一个宏,展开为_Static_assert
。https://en.cppreference.com/w/c/error/static_assert。此外,与您的答案相反,在gcc中c99和c90中不提供`_Static_assert`(仅在gnu99和gnu90中)。这符合标准。基本上,您做了很多额外的工作,只有在使用gnu90和gnu99编译时才带来好处,并且使实际用例微不足道。 - Kami Kaze_Static_assert
在gcc的c90/c99中被定义,但“问题”是它与c11的工作方式不同。它似乎类似于Paolo.Bolzoni在他的答案中提出的负位域。你可以澄清一下(我需要编辑答案以撤回DV)。 - Kami KazeSTATIC_ASSERT()
宏,它可以在所有版本的C和C++中工作(已经使用gcc编译器进行了测试)!对我来说,最大的突破是想出了一个静态断言,在C++ pre-C++11中也能够工作,因为这是我最新发现之前缺失的部分。 - Gabriel Staples尽管问题明确提到了gcc,但为了完整起见,这里提供了针对Microsoft编译器的调整方法。
使用负大小数组typedef并不能说服cl输出一个合理的错误。它只会显示error C2118: negative subscript
。在这方面,零宽位域表现更好。由于这涉及到对结构体进行typedef,我们真的需要使用唯一的类型名称。 __LINE__
无法胜任 - 可能会在头文件和源文件中的同一行上有COMPILE_TIME_ASSERT()
,你的编译将会失败。__COUNTER__
来拯救(自从gcc 4.3以来已经支持)。
#define CTASTR2(pre,post) pre ## post
#define CTASTR(pre,post) CTASTR2(pre,post)
#define STATIC_ASSERT(cond,msg) \
typedef struct { int CTASTR(static_assertion_failed_,msg) : !!(cond); } \
CTASTR(static_assertion_failed_,__COUNTER__)
现在
STATIC_ASSERT(sizeof(long)==7, use_another_compiler_luke)
cl
的结果如下:
错误 C2149: 'static_assertion_failed_use_another_compiler_luke' : 命名的位域不能有零宽度
Gcc也会给出一个易懂的信息:
错误:位域“static_assertion_failed_use_another_compiler_luke”的宽度为零
来自维基百科:
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
它之所以有效,是因为如果断言为真,则数组的大小为1且有效,但如果为假,则大小为-1会导致编译错误。
大多数编译器将显示变量名并指向代码中正确的部分,您可以在其中留下有关断言的注释。
#define STATIC_ASSERT()
类型宏,并提供更通用的示例和使用 STATIC_ASSERT()
的示例编译器输出,这将为您赢得更多的赞并使这种技术更有意义。 - Gabriel Staplestypedef
的解决方案:// Do NOT do this
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]
typedef
关键字的数组声明不能保证在编译时评估。例如,在块作用域中的以下代码将编译:int invalid_value = 0;
STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not);
我建议使用以下方式(在C99中)代替:
// Do this instead
#define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1]
由于使用了static
关键字,该数组将在编译时定义。请注意,此断言仅适用于在编译时评估的COND
。它将无法处理基于内存中的值(例如分配给变量的值)的条件,因此编译将失败。
__attribute__((unused))
关闭。我将其与typedef解决方案进行了比较,编译器生成了完全相同的代码,很可能是因为该变量未被使用。因此,这不会增加内存需求。 - MarcH/* Trickery to create a unique variable name */
#define BOOST_JOIN( X, Y ) BOOST_DO_JOIN( X, Y )
#define BOOST_DO_JOIN( X, Y ) BOOST_DO_JOIN2( X, Y )
#define BOOST_DO_JOIN2( X, Y ) X##Y
#define STATIC_ASSERT(x) typedef char \
BOOST_JOIN( BOOST_JOIN(level_,__INCLUDE_LEVEL__), \
BOOST_JOIN(_assert_on_line_,__LINE__) ) [(x) ? 1 : -1]
static_assert
(C89、C++98 或更高版本):#define CONCAT_(prefix, suffix) prefix##suffix
#define CONCAT(prefix, suffix) CONCAT_(prefix, suffix)
#define outscope_assert(expr) \
struct CONCAT(__outscope_assert_, __COUNTER__) \
{ \
char \
outscope_assert \
[2*(expr)-1]; \
\
} CONCAT(__outscope_assert_, __COUNTER__)
像这样调用:
outscope_assert( 0 > 5 );
兼容C89:
gcc main.c -o main.exe -std=c89
或者C++98:
g++ main.c -o main.exe -std=c++98
它产生的错误看起来像这样:
main.c:32:9: error: size of array 'outscope_assert' is negative
32 | outscope_assert \
| ^~~~~~~~~~~~~~~
main.c:50:1: note: in expansion of macro 'outscope_assert'
50 | outscope_assert(2 > 5);
| ^~~~~~~~~~~~~~~
它不像其他提出的解决方案那样容易出现问题,您可以进行测试!
/* Some test */
/* Global */
outscope_assert(1 < 2);
/* Within Struct */
struct A
{
int a;
outscope_assert(1 < 2);
outscope_assert(2 > 1); outscope_assert(2 > 1); /* Same Line */
};
int main (void)
{
/* Within Function */
outscope_assert(2 > 1);
return 0;
}
__COUNTER__
:__COUNTER__
是一个宏,在编译期间单调递增(从0开始),通常用于生成ID。
该宏可能会根据编译器和/或其版本而定义或未定义,尽管基本上所有现代编译器都支持它。
如果您被困在使用某些恐龙或独角兽般的编译器上,则必须使用替代方法:
__COUNTER__
替换为__LINE__
,但问题在于您将无法在同一行上再次使用静态断言,并且在不同文件中也可能遇到问题。__COUNTER__
替换为my_id
,并将其作为宏函数定义的参数添加,如下所示:#define outscope_assert(expr, myid)
,然后像这样调用宏:outscope_assert(0 < 1 , this_is_myid );
inscope_assert
inscope_assert
?#define inscope_assert(expr) \
{ \
char \
inscope_assert \
[2*(expr)-1]; \
(void)inscope_assert; \
}
你可能已经注意到了,inscope_assert
与 outscope_assert
几乎相同,只是没有了 struct
和 id
这些东西。
这个显然有缺点:它既不能在全局范围内使用,也不能在结构体内使用,但是如果你不能使用 __COUNTER__
的话,你可以放心使用它,因为它不会产生任何副作用,并且保证了严格的 C89 标准。
你可能已经注意到(也可能没有),如果我们在编译时使用更严格的标志:
gcc main.c -o main.exe -std=c89 -Wall -Wextra -ansi -pedantic
我们在 main
声明的 outscope_assert
给我们带来了一个 warning
:
warning: unused variable '__outscope_assert__9' [-Wunused-variable]
简单地告诉我们声明了一个名为struct的变量但是没有使用,这并不是什么大问题,但是我们的inscope_assert
将无法产生它。
C11 standard adds the _Static_assert keyword. _Static_assert ( expression , message )
C23 standard adds the static_assert keyword
static_assert ( expression , message )
or static_assert ( expression )
gcc-4.6 adds the _Static_assert keyword for all versions of C, including c90, c99, c11, c17, etc.
所以只需进行一些预先检查:
#if !defined static_assert
/* C11 or later */
#if defined _Static_assert
#define static_assert _Static_assert
/* GCC 4.6 or later */
#elif defined __GNUC__ && ( __GNUC__ > 4 || __GNUC__ == 4 && defined __GNUC_MINOR__ && __GNUC_MINOR >= 6)
/* It will work but it will throw a warning:
warning: ISO C90 does not support '_Static_assert' [-Wpedantic]
*/
#define static_assert _Static_assert
#endif
#endif
展开宏后,它看起来像这样:
struct __outscope_assert_24 { char outscope_assert [2*(2 > 1)-1]; } __outscope_assert_25
2>1
)为假时抛出错误:error: size of array 'outscope_assert' is negative
表达式(2 > 1)
的值为1,因为true,所以2*1 - 1 = 1
,我们声明了一个大小为1的char
数组。
表达式(2 > 9)
的值为0,因为false,所以2*0 - 1 = -1
,我们声明了一个大小为-1的char
数组。这将在编译时引发错误。
__outscope_assert_24
和__outscope_assert_25
是结构体名称和变量名称,省略第一个会在g++中引发警告,省略第二个会导致在同一作用域中声明两个assert时出现重复成员错误。
宏CONCAT
和CONCAT_
用于创建结构体和变量名称。
来自Perl,具体来说是perl.h
第3455行(之前已经包含了<assert.h>
):
/* STATIC_ASSERT_DECL/STATIC_ASSERT_STMT are like assert(), but for compile
time invariants. That is, their argument must be a constant expression that
can be verified by the compiler. This expression can contain anything that's
known to the compiler, e.g. #define constants, enums, or sizeof (...). If
the expression evaluates to 0, compilation fails.
Because they generate no runtime code (i.e. their use is "free"), they're
always active, even under non-DEBUGGING builds.
STATIC_ASSERT_DECL expands to a declaration and is suitable for use at
file scope (outside of any function).
STATIC_ASSERT_STMT expands to a statement and is suitable for use inside a
function.
*/
#if (defined(static_assert) || (defined(__cplusplus) && __cplusplus >= 201103L)) && (!defined(__IBMC__) || __IBMC__ >= 1210)
/* static_assert is a macro defined in <assert.h> in C11 or a compiler
builtin in C++11. But IBM XL C V11 does not support _Static_assert, no
matter what <assert.h> says.
*/
# define STATIC_ASSERT_DECL(COND) static_assert(COND, #COND)
#else
/* We use a bit-field instead of an array because gcc accepts
'typedef char x[n]' where n is not a compile-time constant.
We want to enforce constantness.
*/
# define STATIC_ASSERT_2(COND, SUFFIX) \
typedef struct { \
unsigned int _static_assertion_failed_##SUFFIX : (COND) ? 1 : -1; \
} _static_assertion_failed_##SUFFIX PERL_UNUSED_DECL
# define STATIC_ASSERT_1(COND, SUFFIX) STATIC_ASSERT_2(COND, SUFFIX)
# define STATIC_ASSERT_DECL(COND) STATIC_ASSERT_1(COND, __LINE__)
#endif
/* We need this wrapper even in C11 because 'case X: static_assert(...);' is an
error (static_assert is a declaration, and only statements can have labels).
*/
#define STATIC_ASSERT_STMT(COND) STMT_START { STATIC_ASSERT_DECL(COND); } STMT_END
<assert.h>
中提供了static_assert
,则使用它。否则,如果条件为假,则声明一个大小为负的位域,导致编译失败。
STMT_START
/ STMT_END
是宏,分别扩展为do
/ while (0)
。