Ways to ASSERT expressions at build time in C
我正在整理一些旧的代码,这些代码使用"幻数"来设置硬件寄存器,我想使用常量而不是这些数字来使代码更具表现力(实际上它们将映射到名称 /用于记录寄存器的值)。
但是,我担心随着变化的数量,我可能会打破神奇的数字。 这是一个简化的例子(寄存器集更复杂):
1 2 3 4 5 6 7 | const short mode0 = 0; const short mode1 = 1; const short mode2 = 2; const short state0 = 0; const short state1 = 4; const short state2 = 8; |
而不是:
1 | set_register(5); |
我们有:
1 | set_register(state1|mode1); |
我正在寻找的是构建时间版本:
1 | ASSERT(5==(state1|mode1)); |
更新
@Christian,感谢快速响应,我对C / non-boost环境的答案感兴趣,因为这是驱动程序/内核代码。
新答案:
在我的原始答案(下面)中,我必须有两个不同的宏来支持函数作用域和全局作用域中的断言。我想知道是否有可能提出一个适用于两个范围的解决方案。
我能够找到一个适用于使用外部字符数组的Visual Studio和Comeau编译器的解决方案。但我能够找到适用于GCC的更复杂的解决方案。但GCC的解决方案不适用于Visual Studio。 :(但添加一个'#ifdef __ GNUC __',很容易为给定的编译器选择正确的宏集。
解:
1 2 3 4 5 6 7 8 9 10 | #ifdef __GNUC__ #define STATIC_ASSERT_HELPER(expr, msg) \ (!!sizeof \ (struct { unsigned int STATIC_ASSERTION__##msg: (expr) ? 1 : -1; })) #define STATIC_ASSERT(expr, msg) \ extern int (*assert_function__(void)) [STATIC_ASSERT_HELPER(expr, msg)] #else #define STATIC_ASSERT(expr, msg) \ extern char STATIC_ASSERTION__##msg[1]; \ extern char STATIC_ASSERTION__##msg[(expr)?1:2] #endif /* #ifdef __GNUC__ */ |
以下是test.c第22行为
GCC:
1 | line 22: error: negative width in bit-field `STATIC_ASSERTION__test_message' |
视觉工作室:
1 2 | test.c(22) : error C2369: 'STATIC_ASSERTION__test_message' : redefinition; different subscripts test.c(22) : see declaration of 'STATIC_ASSERTION__test_message' |
科莫:
1 2 | line 22: error: declaration is incompatible with "char STATIC_ASSERTION__test_message[1]" (declared at line 22) |
原始答案:
我做了一些非常类似于Checkers的事情。但是我在许多编译器中都会显示一条消息:
1 2 3 4 5 | #define STATIC_ASSERT(expr, msg) \ { \ char STATIC_ASSERTION__##msg[(expr)?1:-1]; \ (void)STATIC_ASSERTION__##msg[0]; \ } |
并且为了在全局范围内(在函数外部)执行某些操作,请使用以下命令:
1 2 3 | #define GLOBAL_STATIC_ASSERT(expr, msg) \ extern char STATIC_ASSERTION__##msg[1]; \ extern char STATIC_ASSERTION__##msg[(expr)?1:2] |
有一篇文章
Ralf Holly在C中检查静态断言的不同选项。
他介绍了三种不同的方法:
- switch case值必须是唯一的
- 数组不得有负尺寸
- 对于常量表达式除以零
他对最佳实施的结论如下:
1 2 3 4 | #define assert_static(e) \ do { \ enum { assert_static__ = 1/(e) }; \ } while (0) |
如果您无权访问第三方库静态断言函数(如boost),则可以滚动自己的静态断言:
1 2 3 4 | #define STATIC_ASSERT(x) \ do { \ const static char dummy[(x)?1:-1] = {0};\ } while(0) |
当然,缺点是错误信息不会非常有用,但至少,它会为您提供行号。
Checkout boost的静态断言
1 2 | #define static_assert(expr) \ int __static_assert(int static_assert_failed[(expr)?1:-1]) |
它可以在任何地方,任何时间使用。
我认为这是最简单的解决方案。
使用前,请仔细使用您的编译器进行测试。
如果你有Boost,那么使用
这是我的
它应该是更复杂的因为在ANSI C代码中你需要2个不同的宏 - 一个可以在你有声明的区域工作,一个可以在正常语句所在的区域工作。还有一些工作可以使宏在全局范围或块范围内工作,以及一堆gunk以确保没有名称冲突。
对于C ++代码(或允许声明与语句混合的C99代码),
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | /* Define macros to allow compile-time assertions. If the expression is false, an error something like test.c(9) : error XXXXX: negative subscript will be issued (the exact error and its format is dependent on the compiler). The techique used for C is to declare an extern (which can be used in file or block scope) array with a size of 1 if the expr is TRUE and a size of -1 if the expr is false (which will result in a compiler error). A counter or line number is appended to the name to help make it unique. Note that this is not a foolproof technique, but compilers are supposed to accept multiple identical extern declarations anyway. This technique doesn't work in all cases for C++ because extern declarations are not permitted inside classes. To get a CPP_ASSERT(), there is an implementation of something similar to Boost's BOOST_STATIC_ASSERT(). Boost's approach uses template specialization; when expr evaluates to 1, a typedef for the type ::interslice::StaticAssert_test< sizeof( ::interslice::StaticAssert_failed<true>) > which boils down to ::interslice::StaticAssert_test< 1> which boils down to struct StaticAssert_test is declared. If expr is 0, the compiler will be unable to find a specialization for ::interslice::StaticAssert_failed<false>. STATIC_ASSERT() or C_ASSERT should work in either C or C++ code (and they do the same thing) CPP_ASSERT is defined only for C++ code. Since declarations can only occur at file scope or at the start of a block in standard C, the C_ASSERT() or STATIC_ASSERT() macros will only work there. For situations where you want to perform compile-time asserts elsewhere, use C_ASSERT_EX() or STATIC_ASSERT_X() which wrap an enum declaration inside it's own block. */ #ifndef C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546 #define C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546 /* first some utility macros to paste a line number or counter to the end of an identifier * this will let us have some chance of generating names that are unique * there may be problems if a static assert ends up on the same line number in different headers * to avoid that problem in C++ use namespaces */ #if !defined( PASTE) #define PASTE2( x, y) x##y #define PASTE( x, y) PASTE2( x, y) #endif /* PASTE */ #if !defined( PASTE_LINE) #define PASTE_LINE( x) PASTE( x, __LINE__) #endif /* PASTE_LINE */ #if!defined( PASTE_COUNTER) #if (_MSC_VER >= 1300) /* __COUNTER__ introduced in VS 7 (VS.NET 2002) */ #define PASTE_COUNTER( x) PASTE( x, __COUNTER__) /* __COUNTER__ is a an _MSC_VER >= 1300 non-Ansi extension */ #else #define PASTE_COUNTER( x) PASTE( x, __LINE__) /* since there's no __COUNTER__ use __LINE__ as a more or less reasonable substitute */ #endif #endif /* PASTE_COUNTER */ #if __cplusplus extern"C++" { // required in case we're included inside an extern"C" block namespace interslice { template<bool b> struct StaticAssert_failed; template<> struct StaticAssert_failed<true> { enum {val = 1 }; }; template<int x> struct StaticAssert_test { }; } } #define CPP_ASSERT( expr) typedef ::interslice::StaticAssert_test< sizeof( ::interslice::StaticAssert_failed< (bool) (expr) >) > PASTE_COUNTER( IntersliceStaticAssertType_) #define STATIC_ASSERT( expr) CPP_ASSERT( expr) #define STATIC_ASSERT_EX( expr) CPP_ASSERT( expr) #else #define C_ASSERT_STORAGE_CLASS extern /* change to typedef might be needed for some compilers? */ #define C_ASSERT_GUID 4964f7ac50fa4661a1377e4c17509495 /* used to make sure our extern name doesn't collide with something else */ #define STATIC_ASSERT( expr) C_ASSERT_STORAGE_CLASS char PASTE( PASTE( c_assert_, C_ASSERT_GUID), [(expr) ? 1 : -1]) #define STATIC_ASSERT_EX(expr) do { enum { c_assert__ = 1/((expr) ? 1 : 0) }; } while (0) #endif /* __cplusplus */ #if !defined( C_ASSERT) /* C_ASSERT() might be defined by winnt.h */ #define C_ASSERT( expr) STATIC_ASSERT( expr) #endif /* !defined( C_ASSERT) */ #define C_ASSERT_EX( expr) STATIC_ASSERT_EX( expr) #ifdef TEST_IMPLEMENTATION C_ASSERT( 1 < 2); C_ASSERT( 1 < 2); int main( ) { C_ASSERT( 1 < 2); C_ASSERT( 1 < 2); int x; x = 1 + 4; C_ASSERT_EX( 1 < 2); C_ASSERT_EX( 1 < 2); return( 0); } #endif /* TEST_IMPLEMENTATION */ #endif /* C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546 */ |
此处列出的任何技术都应该有效,当C ++ 0x可用时,您将能够使用内置的
尝试:
1 2 3 4 | #define STATIC_ASSERT(x, error) \ do { \ static const char error[(x)?1:-1];\ } while(0) |
然后你可以写:
1 | STATIC_ASSERT(a == b, a_not_equal_to_b); |
这可能会给你一个更好的错误信息(取决于你的编译器)。
常见的便携式选项是
1 2 3 | #if 5 != (state1|mode1) # error"aaugh!" #endif |
但它在这种情况下不起作用,因为它们是C常量而不是
你可以看到Linux内核的
1 | #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) |
当
确保使用足够新的编译器(例如
那你的陈述就是:
1 | _Static_assert(state1|mode1 == 5,"Unexpected change of bitflags"); |