我正在整理一些使用“幻数”的旧代码来设置硬件寄存器,我想使用常量代替这些数字来使代码更具表现力(实际上它们会映射到用于记录寄存器的名称/值。)
然而,我担心随着变化的数量,我可能会打破神奇的数字。这是一个简化的例子(寄存器集更复杂):
const short mode0 = 0;
const short mode1 = 1;
const short mode2 = 2;
const short state0 = 0;
const short state1 = 4;
const short state2 = 8;
所以代替:
set_register(5);
我们有:
set_register(state1|mode1);
我正在寻找的是构建时间版本:
ASSERT(5==(state1|mode1));
更新
@Christian,感谢快速响应,我对C / non-boost环境的答案感兴趣,因为这是驱动程序/内核代码。
答案 0 :(得分:27)
新答案:
在我的原始答案(下面)中,我必须有两个不同的宏来支持函数作用域和全局作用域中的断言。我想知道是否有可能提出一个适用于两个范围的解决方案。
我能够找到一个适用于使用外部字符数组的Visual Studio和Comeau编译器的解决方案。但我能够找到适用于GCC的更复杂的解决方案。但GCC的解决方案不适用于Visual Studio。 :(但是添加一个'#ifdef __ GNUC __',很容易为给定的编译器选择正确的宏集。
<强>解决方案:强>
#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行报告的STATIC_ASSERT(1==1, test_message);
错误消息:
<强> GCC 强>
line 22: error: negative width in bit-field `STATIC_ASSERTION__test_message'
Visual Studio:
test.c(22) : error C2369: 'STATIC_ASSERTION__test_message' : redefinition; different subscripts
test.c(22) : see declaration of 'STATIC_ASSERTION__test_message'
<强>科莫:强>
line 22: error: declaration is incompatible with
"char STATIC_ASSERTION__test_message[1]" (declared at line 22)
原始回答:
我做的事与Checkers的做法非常相似。但是我在很多编译器中都会显示一条消息:
#define STATIC_ASSERT(expr, msg) \
{ \
char STATIC_ASSERTION__##msg[(expr)?1:-1]; \
(void)STATIC_ASSERTION__##msg[0]; \
}
要在全局范围内(在函数外部)执行某些操作,请使用:
#define GLOBAL_STATIC_ASSERT(expr, msg) \
extern char STATIC_ASSERTION__##msg[1]; \
extern char STATIC_ASSERTION__##msg[(expr)?1:2]
答案 1 :(得分:14)
有一篇文章 Ralf Holly检查C中静态断言的不同选项。
他提出了三种不同的方法:
他对最佳实施的结论如下:
#define assert_static(e) \
do { \
enum { assert_static__ = 1/(e) }; \
} while (0)
答案 2 :(得分:11)
Checkout boost static assert
答案 3 :(得分:11)
如果您无法访问第三方库静态断言函数(如boost),则可以滚动自己的静态断言:
#define STATIC_ASSERT(x) \
do { \
const static char dummy[(x)?1:-1] = {0};\
} while(0)
当然,缺点是错误消息不会非常有用,但至少它会为您提供行号。
答案 4 :(得分:8)
#define static_assert(expr) \
int __static_assert(int static_assert_failed[(expr)?1:-1])
它可以在任何地方,任何时间使用。 我认为这是最简单的解决方案。
使用前,请仔细使用您的编译器进行测试。
答案 5 :(得分:5)
尝试:
#define STATIC_ASSERT(x, error) \
do { \
static const char error[(x)?1:-1];\
} while(0)
然后你可以写:
STATIC_ASSERT(a == b, a_not_equal_to_b);
这可能会为您提供更好的错误消息(取决于您的编译器)。
答案 6 :(得分:5)
此处列出的任何技术均可使用,当C ++ 0x可用时,您将可以使用内置的static_assert
关键字。
答案 7 :(得分:5)
如果你有Boost,那么使用BOOST_STATIC_ASSERT
是可行的方法。如果你正在使用C或者不想获得Boost
这是我的c_assert.h
文件,用于定义(并解释)一些用于处理静态断言的宏的工作方式。
它应该是更复杂的因为在ANSI C代码中你需要2个不同的宏 - 一个可以在你有声明的区域工作,一个可以在正常语句所在的区域工作。还有一些工作可以使宏在全局范围或块范围内工作,以及一堆gunk以确保没有名称冲突。
STATIC_ASSERT()
可用于变量声明块或全局范围。
STATIC_ASSERT_EX()
可以是常规陈述。
对于C ++代码(或允许声明与语句混合的C99代码)STATIC_ASSERT()
可以在任何地方使用。
/*
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 */
答案 8 :(得分:3)
常见的便携式选项是
#if 5 != (state1|mode1)
# error "aaugh!"
#endif
但在这种情况下它不起作用,因为它们是C常量而不是#define
s。
您可以看到Linux内核的BUILD_BUG_ON
宏用于处理您案例的内容:
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
当condition
为真时,这变为((void)sizeof(char[-1]))
,这是非法的,应该在编译时失败,否则变为((void)sizeof(char[1]))
,这很好。
答案 9 :(得分:1)
确保使用足够新的编译器进行编译(例如gcc -std=c11
)。
然后你的陈述就是:
_Static_assert(state1|mode1 == 5, "Unexpected change of bitflags");
答案 10 :(得分:1)
old= array([[ 65, 108, 105, 32, 105, 115, 32, 116, 104, 101, 32,66,101,
115, 116, 32, 105, 110, 32, 116, 104, 101, 32, 119, 111, 114,
108, 100, 32, 121, 101, 101, 104, 104, 104, 104, 104, 104, 104,
104, ..., 170, 170, 170],
[171, 171, 171, ..., 170, 170, 170],
[171, 171, 171, ..., 170, 170, 170],
...,
[ 17, 17, 17, ..., 17, 17, 17],
[ 17, 17, 17, ..., 17, 17, 17],
[ 17, 17, 17, ..., 17, 17, 17]], dtype=uint8)
这不像单行MY_ASSERT(expr)解决方案那样优雅。在编译C代码之前,可以使用sed,awk或m4宏处理器来生成MY_ASSERT(expr)的DEBUG代码扩展为多行或NODEBUG代码以将其删除以进行生产。