在 C 中构建时断言表达式的方法

发布于 2024-07-07 11:58:52 字数 625 浏览 9 评论 0 原文

我正在整理一些旧代码,这些代码到处使用“幻数”来设置硬件寄存器,并且我想使用常量而不是这些数字来使代码更具表现力(事实上,它们将映射到名称/用于记录寄存器的值)。

然而,我担心随着变化量的增加,我可能会打破神奇的数字。 这是一个简化的示例(寄存器集更复杂):

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/非升压环境答案感兴趣,因为这是驱动程序/内核代码。

I'm tidying up some older code that uses 'magic numbers' all over the place to set hardware registers, and I would like to use constants instead of these numbers to make the code somewhat more expressive (in fact they will map to the names/values used to document the registers).

However, I'm concerned that with the volume of changes I might break the magic numbers. Here is a simplified example (the register set is more complex):

const short mode0 = 0;
const short mode1 = 1;
const short mode2 = 2;

const short state0 = 0;
const short state1 = 4;
const short state2 = 8;

so instead of :

set_register(5);

we have:

set_register(state1|mode1);

What I'm looking for is a build time version of:

ASSERT(5==(state1|mode1));

Update

@Christian, thanks for the quick response, I'm interested on a C / non-boost environment answer too because this is driver/kernel code.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

晚风撩人 2024-07-14 11:58:52

新答案

在我原来的答案(如下)中,我必须有两个不同的宏来支持函数范围和全局范围内的断言。 我想知道是否有可能提出一个适用于这两个范围的单一解决方案。

我找到了一个适用于使用外部字符数组的 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__ */

以下是 STATIC_ASSERT(1= 报告的错误消息) =1, test_message); 在 test.c 的第 22 行:

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'

Comeau:

line 22: error: declaration is incompatible with
        "char STATIC_ASSERTION__test_message[1]" (declared at line 22)

 
 

原始答案

我做的事情与跳棋非常相似。 但我包含一条将在许多编译器中显示的消息:

#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]

NEW ANSWER :

In my original answer (below), I had to have two different macros to support assertions in a function scope and at the global scope. I wondered if it was possible to come up with a single solution that would work in both scopes.

I was able to find a solution that worked for Visual Studio and Comeau compilers using extern character arrays. But I was able to find a more complex solution that works for GCC. But GCC's solution doesn't work for Visual Studio. :( But adding a '#ifdef __ GNUC __', it's easy to choose the right set of macros for a given compiler.

Solution:

#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__ */

Here are the error messages reported for STATIC_ASSERT(1==1, test_message); at line 22 of test.c:

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'

Comeau:

line 22: error: declaration is incompatible with
        "char STATIC_ASSERTION__test_message[1]" (declared at line 22)

 
 

ORIGINAL ANSWER :

I do something very similar to what Checkers does. But I include a message that'll show up in many compilers:

#define STATIC_ASSERT(expr, msg)               \
{                                              \
    char STATIC_ASSERTION__##msg[(expr)?1:-1]; \
    (void)STATIC_ASSERTION__##msg[0];          \
}

And for doing something at the global scope (outside a function) use this:

#define GLOBAL_STATIC_ASSERT(expr, msg)   \
  extern char STATIC_ASSERTION__##msg[1]; \
  extern char STATIC_ASSERTION__##msg[(expr)?1:2]
折戟 2024-07-14 11:58:52

有一篇文章由
Ralf Holly 检查 C 中静态断言的不同选项。

他提出了三种不同的方法:

  • switch case 值必须是 对于常量表达式,唯一
  • 数组不得具有被
  • 零除的

负维度他的最佳实现的结论是:

#define assert_static(e) \
    do { \
        enum { assert_static__ = 1/(e) }; \
    } while (0)

There is an article by
Ralf Holly that examines different options for static asserts in C.

He presents three different approaches:

  • switch case values must be unique
  • arrays must not have negative dimensions
  • division by zero for constant expressions

His conclusion for the best implementation is this:

#define assert_static(e) \
    do { \
        enum { assert_static__ = 1/(e) }; \
    } while (0)
毅然前行 2024-07-14 11:58:52

查看 boost 的静态断言

Checkout boost's static assert

乖乖 2024-07-14 11:58:52

如果您无法访问第三方库静态断言函数(如 boost),您可以推出自己的静态断言:

#define STATIC_ASSERT(x) \
    do { \
        const static char dummy[(x)?1:-1] = {0};\
    } while(0)

当然,缺点是错误消息不会很有帮助,但至少,它会给你行号。

You can roll your own static assert if you don't have access to a third-party library static assert function (like boost):

#define STATIC_ASSERT(x) \
    do { \
        const static char dummy[(x)?1:-1] = {0};\
    } while(0)

The downside is, of course, that error message is not going to be very helpful, but at least, it will give you the line number.

苦行僧 2024-07-14 11:58:52
#define static_assert(expr) \
int __static_assert(int static_assert_failed[(expr)?1:-1])

它可以随时随地使用。
我认为这是最简单的解决方案。

使用之前,请使用编译器仔细测试它。

#define static_assert(expr) \
int __static_assert(int static_assert_failed[(expr)?1:-1])

It can be used anywhere, any times.
I think it is the easiest solution.

Before usage, test it with your compiler carefully.

芯好空 2024-07-14 11:58:52

此处列出的任何技术都应该有效,并且当 C++0x 可用时,您将能够使用内置的 static_assert 关键字。

Any of the techniques listed here should work and when C++0x becomes available you will be able to use the built-in static_assert keyword.

糖粟与秋泊 2024-07-14 11:58:52

如果您有 Boost,那么使用 BOOST_STATIC_ASSERT 是最佳选择。 如果您使用 C 或者不想获得 Boost
这是我的 c_assert.h 文件,它定义(并解释了其工作原理)一些处理静态断言的宏。

它应该更复杂一些,因为在 ANSI C 代码中,您需要 2 个不同的宏 - 一个可以在您有声明的区域中工作,另一个可以在普通语句所在的区域中工作。 还有一些工作需要让宏在全局范围或块范围内工作,以及一堆垃圾以确保不存在名称冲突。

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 */

If you have Boost then using BOOST_STATIC_ASSERT is the way to go. If you're using C or don't want to get Boost
here's my c_assert.h file that defines (and explains the workings of) a few macros to handle static assertions.

It's a bit more convoluted that it should be because in ANSI C code you need 2 different macros - one that can work in the area where you have declarations and one that can work in the area where normal statements go. There is a also a bit of work that goes into making the macro work at global scope or in block scope and a bunch of gunk to ensure that there are no name collisions.

STATIC_ASSERT() can be used in the variable declaration block or global scope.

STATIC_ASSERT_EX() can be among regular statements.

For C++ code (or C99 code that allow declarations mixed with statements) STATIC_ASSERT() will work anywhere.

/*
    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 */
笑脸一如从前 2024-07-14 11:58:52

尝试:

#define STATIC_ASSERT(x, error) \
do { \
    static const char error[(x)?1:-1];\
} while(0)

然后你可以写:

STATIC_ASSERT(a == b, a_not_equal_to_b);

这可能会给你一个更好的错误消息(取决于你的编译器)。

Try:

#define STATIC_ASSERT(x, error) \
do { \
    static const char error[(x)?1:-1];\
} while(0)

Then you can write:

STATIC_ASSERT(a == b, a_not_equal_to_b);

Which may give you a better error message (depending on your compiler).

忆梦 2024-07-14 11:58:52

常见的、可移植的选项是,

#if 5 != (state1|mode1)
#    error "aaugh!"
#endif

但在这种情况下不起作用,因为它们是 C 常量,而不是 #define

您可以查看 Linux 内核的 BUILD_BUG_ON 宏来处理您的情况:

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

condition 为 true 时,这将变为 ((void)sizeof(char[-1] )),这是非法的,应该在编译时失败,否则它就变成 ((void)sizeof(char[1])),这样就可以了。

The common, portable option is

#if 5 != (state1|mode1)
#    error "aaugh!"
#endif

but it doesn't work in this case, because they're C constants and not #defines.

You can see the Linux kernel's BUILD_BUG_ON macro for something that handles your case:

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

When condition is true, this becomes ((void)sizeof(char[-1])), which is illegal and should fail at compile time, and otherwise it becomes ((void)sizeof(char[1])), which is just fine.

晨敛清荷 2024-07-14 11:58:52

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

那么你的陈述很简单:

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

Ensure you compile with a sufficiently recent compiler (e.g. gcc -std=c11).

Then your statement is simply:

_Static_assert(state1|mode1 == 5, "Unexpected change of bitflags");
我的影子我的梦 2024-07-14 11:58:52
#define MODE0 0
#define MODE1 1
#define MODE2 2

#define STATE0 0
#define STATE1 4
#define STATE2 8

set_register(STATE1|STATE1); //set_register(5);
#if (!(5==(STATE1|STATE1))) //MY_ASSERT(5==(state1|mode1)); note the !
#error "error blah blah"
#endif

这不像一行 MY_ASSERT(expr) 解决方案那么优雅。 您可以在编译 C 代码之前使用 sed、awk 或 m4 宏处理器来生成 MY_ASSERT(expr) 的 DEBUG 代码扩展为多行或 NODEBUG 代码,从而在生产中删除它们。

#define MODE0 0
#define MODE1 1
#define MODE2 2

#define STATE0 0
#define STATE1 4
#define STATE2 8

set_register(STATE1|STATE1); //set_register(5);
#if (!(5==(STATE1|STATE1))) //MY_ASSERT(5==(state1|mode1)); note the !
#error "error blah blah"
#endif

This is not as elegant as a one line MY_ASSERT(expr) solution. You could use sed, awk, or m4 macro processor before compiling your C code to generate the DEBUG code expansion of MY_ASSERT(expr) to multiple lines or NODEBUG code which removes them for production.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文