关于重构:在C语言中在构建时ASSERT表达式的方法

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行为STATIC_ASSERT(1==1, test_message);报告的错误消息:

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,那么使用BOOST_STATIC_ASSERT就可以了。如果你正在使用C或者不想获得Boost
这是我的c_assert.h文件,它定义(并解释了一些宏)来处理静态断言的工作原理。

它应该是更复杂的因为在ANSI C代码中你需要2个不同的宏 - 一个可以在你有声明的区域工作,一个可以在正常语句所在的区域工作。还有一些工作可以使宏在全局范围或块范围内工作,以及一堆gunk以确保没有名称冲突。

STATIC_ASSERT()可用于变量声明块或全局范围。

STATIC_ASSERT_EX()可以是常规报表。

对于C ++代码(或允许声明与语句混合的C99代码),STATIC_ASSERT()可以在任何地方使用。

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可用时,您将能够使用内置的static_assert关键字。


尝试:

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常量而不是#define s。

你可以看到Linux内核的BUILD_BUG_ON宏来处理你的情况:

1
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))

condition为真时,它变为((void)sizeof(char[-1])),这是非法的,并且应该在编译时失败,否则它变为((void)sizeof(char[1])),这很好。


确保使用足够新的编译器(例如gcc -std=c11)进行编译。

那你的陈述就是:

1
_Static_assert(state1|mode1 == 5,"Unexpected change of bitflags");