C中的静态断言

时间:2010-08-02 06:32:20

标签: c gcc assert compile-time static-assert

在C(不是C ++)中实现编译时静态断言的最佳方法是什么,特别强调GCC?

12 个答案:

答案 0 :(得分:78)

这适用于功能和非功能范围(但不在结构,联合内部)。

#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); 
}
  1. 如果编译时断言无法匹配,那么GCC sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative

  2. 会生成几乎可理解的消息
  3. 可以或应该更改宏以生成typedef的唯一名称(即__LINE__名称末尾的连接static_assert_...

  4. 而不是三元,也可以使用#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1],它甚至可以在生锈的olde cc65(用于6502 cpu)编译器上工作。

  5. <强>更新 为了完整起见,这里是__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); 
    }
    

    UPDATE2:GCC特定代码

    GCC 4.3(我猜)引入了“错误”和“警告”功能属性。如果通过死代码消除(或其他措施)无法消除对具有该属性的函数的调用,则会生成错误或警告。这可以用于使用用户定义的故障描述来编译时间断言。它仍然是确定如何在名称空间范围内使用它们而不需要使用虚函数:

    #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
    

答案 1 :(得分:71)

C11标准添加_Static_assert关键字。

这是implemented since gcc-4.6

_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */

第一个插槽需要是一个整数常量表达式。第二个槽是一个常量字符串文字,可以很长(_Static_assert(0, L"assertion of doom!"))。

我应该注意到,这也是在最新版本的clang中实现的。

答案 2 :(得分:11)

CL

我知道这个问题明确提到了gcc,但为了完整起见,这是微软编译器的一个调整。

使用负大小的数组typedef并不能说服 cl 吐出一个不错的错误。它只是说error C2118: negative subscript。在这方面,零宽度位域的表现更好。由于这涉及对struct进行类型化,我们确实需要使用唯一的类型名称。 __LINE__没有削减芥末 - 可能在标题和源文件中的同一行上有COMPILE_TIME_ASSERT(),编译将中断。 __COUNTER__来救援(自4.3以来它一直在gcc中)。

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

答案 3 :(得分:4)

来自Wikipedia

#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}

COMPILE_TIME_ASSERT( BOOLEAN CONDITION );

答案 4 :(得分:3)

我会建议通过typedef使用该解决方案:

#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上):

#define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1]

由于使用了static关键字,该数组将在编译时定义。请注意,此断言仅适用于在编译时评估的COND。它将无法基于内存中的值(例如,分配给变量的值)使用条件(即,编译将失败)。

答案 5 :(得分:2)

如果将STATIC_ASSERT()宏与__LINE__一起使用,则可以通过包含__INCLUDE_LEVEL__来避免.c文件中的条目与头文件中的其他条目之间的行号冲突。< / p>

例如:

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

答案 6 :(得分:1)

经典的方法是使用数组:

char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];

它的作用是因为如果断言为true,则数组的大小为1且有效,但如果为false,则大小为-1会产生编译错误。

大多数编译器都会显示变量的名称,并指向代码的右侧部分,您可以在其中留下关于断言的最终注释。

答案 7 :(得分:1)

因为:

    现在,在gcc中为所有C版本定义了
  1. _Static_assert(),并且
  2. static_assert()在C ++ 11和更高版本中定义

因此,STATIC_ASSERT()的以下简单宏适用于:

  1. C ++:
    1. C ++ 11(g++ -std=c++11)或更高版本
  2. C:
    1. gcc -std=c90
    2. gcc -std=c99
    3. gcc -std=c11
    4. gcc(未指定std)

定义STATIC_ASSERT如下:

/* For C++: */
#ifdef __cplusplus
    #ifndef _Static_assert
        #define _Static_assert static_assert /* `static_assert` is part of C++11 or later */
    #endif
#endif
/* Now for gcc (C) (and C++, given the define above): */
#define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")

现在使用它:

STATIC_ASSERT(1 > 2); // Output will look like: error: static assertion failed: "(1 > 2) failed" 

示例:

在Ubuntu上使用gcc 4.8.4进行了测试:

示例1:输出良好的gcc(即:STATIC_ASSERT()代码有效,但条件为false,导致编译时断言):

  

$ gcc -Wall -o static_assert static_assert.c && ./static_assert
  static_assert.c:在“ main”函数中
  static_assert.c:78:38:错误:静态断言失败:“((1> 2))失败”
   #define STATIC_ASSERT(test_for_true)_Static_assert((test_for_true),“(” #test_for_true“)失败”)
                                        ^
  static_assert.c:88:5:注意:在宏STATIC_ASSERT的扩展中
       STATIC_ASSERT(1> 2);
       ^

示例2:输出良好的g++ -std=c++11(即:STATIC_ASSERT()代码有效,但条件为false,导致编译时断言):

  

$ g ++ -Wall -std = c ++ 11 -o static_assert static_assert.c && ./static_assert
  static_assert.c:在函数‘int main()’中
  static_assert.c:74:32:错误:静态断言失败:(1> 2)失败
           #define _Static_assert static_assert / * static_assert是C ++ 11或更高版本的一部分* /
                                  ^
  static_assert.c:78:38:注意:扩展宏_Static_assert
   #define STATIC_ASSERT(test_for_true)_Static_assert((test_for_true),“(” #test_for_true“)失败”)
                                        ^
  static_assert.c:88:5:注意:在宏STATIC_ASSERT的扩展中
       STATIC_ASSERT(1> 2);
       ^

示例3: 失败 C ++输出(即:断言代码根本无法正常工作,因为它使用的是C ++版本 before < / em> C ++ 11):

  

$ g ++ -Wall -o static_assert static_assert.c && ./static_assert
  static_assert.c:88:5:警告:标识符“ static_assert”是C ++ 11中的关键字[-Wc ++ 0x-compat]
       STATIC_ASSERT(1> 2);
       ^
  static_assert.c:在函数‘int main()’中
  static_assert.c:78:99:错误:未在此范围内声明“ static_assert”
   #define STATIC_ASSERT(test_for_true)_Static_assert((test_for_true),“(” #test_for_true“)失败”)
                                                                                                     ^
  static_assert.c:88:5:注意:在宏STATIC_ASSERT的扩展中
       STATIC_ASSERT(1> 2);
       ^

完整的测试结果在这里:

/*
static_assert.c
- test static asserts in C and C++ using gcc compiler

Gabriel Staples
4 Mar. 2019 

To be posted in:
1. https://stackoverflow.com/questions/987684/does-gcc-have-a-built-in-compile-time-assert/987756#987756
2. https://stackoverflow.com/questions/3385515/static-assert-in-c/7287341#7287341

To compile & run:
  C:
    gcc -Wall -o static_assert static_assert.c && ./static_assert
    gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert
    gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert
    gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert
  C++:
    g++ -Wall -o static_assert static_assert.c && ./static_assert
    g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert
    g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert
    g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert

-------------
TEST RESULTS:
-------------

1. `_Static_assert(false, "1. that was false");` works in:
  C:
    gcc -Wall -o static_assert static_assert.c && ./static_assert             YES
    gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert    YES
    gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert    YES
    gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert    YES
  C++:
    g++ -Wall -o static_assert static_assert.c && ./static_assert             NO
    g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert  NO

2. `static_assert(false, "2. that was false");` works in:
  C:
    gcc -Wall -o static_assert static_assert.c && ./static_assert             NO
    gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert    NO
    gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert    NO
    gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert    NO
  C++:
    g++ -Wall -o static_assert static_assert.c && ./static_assert             NO
    g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert  YES

3. `STATIC_ASSERT(1 > 2);` works in:
  C:
    gcc -Wall -o static_assert static_assert.c && ./static_assert             YES
    gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert    YES
    gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert    YES
    gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert    YES
  C++:
    g++ -Wall -o static_assert static_assert.c && ./static_assert             NO
    g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert  NO
    g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert  YES

*/

#include <stdio.h>
#include <stdbool.h>

/* For C++: */
#ifdef __cplusplus
    #ifndef _Static_assert
        #define _Static_assert static_assert /* `static_assert` is part of C++11 or later */
    #endif
#endif
/* Now for gcc (C) (and C++, given the define above): */
#define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")


int main(void)
{
    printf("Hello World\n");

    /*_Static_assert(false, "1. that was false");*/
    /*static_assert(false, "2. that was false");*/

    STATIC_ASSERT(1 > 2);

    return 0;
}

答案 8 :(得分:0)

对于那些想要一些非常基本且可移植但却无法访问C ++ 11功能的人,我只写了这个东西。
通常使用STATIC_ASSERT(如果需要,可以在同一个函数中编写两次),并在函数外部使用GLOBAL_STATIC_ASSERT作为第一个参数。

#if defined(static_assert)
#   define STATIC_ASSERT static_assert
#   define GLOBAL_STATIC_ASSERT(a, b, c) static_assert(b, c)
#else
#   define STATIC_ASSERT(pred, explanation); {char assert[1/(pred)];(void)assert;}
#   define GLOBAL_STATIC_ASSERT(unique, pred, explanation); namespace ASSERTATION {char unique[1/(pred)];}
#endif

GLOBAL_STATIC_ASSERT(first, 1, "Hi");
GLOBAL_STATIC_ASSERT(second, 1, "Hi");

int main(int c, char** v) {
    (void)c; (void)v;
    STATIC_ASSERT(1 > 0, "yo");
    STATIC_ASSERT(1 > 0, "yo");
//    STATIC_ASSERT(1 > 2, "yo"); //would compile until you uncomment this one
    return 0;
}

说明:
<子> 首先,它检查你是否有真正的断言,如果它可用,你肯定想要使用它 如果不这样做,则通过获取pred icate并将其自行划分来断言。这样做有两件事 如果它为零,id est,则断言失败,它将导致除零错误(算法被强制,因为它试图声明一个数组)。
如果它不为零,则将数组大小标准化为1。因此,如果断言通过,你不会希望它失败,因为你的谓词评估为-1(无效),或者是232442(大量浪费空间,IDK如果它会被优化掉) 。
对于STATIC_ASSERT,它用大括号括起来,这使它成为一个块,它对变量assert进行范围定义,这意味着你可以多次写入它。
它还将其转换为void,这是摆脱unused variable警告的已知方法。
对于GLOBAL_STATIC_ASSERT,它不是在代码块中,而是生成命名空间。命名空间允许在函数之外。如果您多次使用此定义,则需要unique标识符来停止任何冲突的定义。


在GCC和VS'12 C ++上为我工作

答案 9 :(得分:0)

这是有效的,&#34;删除未使用的&#34;选项集。我可以使用一个全局函数来检查全局参数。

//
#ifndef __sassert_h__
#define __sassert_h__

#define _cat(x, y) x##y

#define _sassert(exp, ln) \
extern void _cat(ASSERT_WARNING_, ln)(void); \
if(!(exp)) \
{ \
    _cat(ASSERT_WARNING_, ln)(); \
}

#define sassert(exp) _sassert(exp, __LINE__)

#endif //__sassert_h__

//-----------------------------------------
static bool tab_req_set_relay(char *p_packet)
{
    sassert(TXB_TX_PKT_SIZE < 3000000);
    sassert(TXB_TX_PKT_SIZE >= 3000000);
    ...
}

//-----------------------------------------
Building target: ntank_app.elf
Invoking: Cross ARM C Linker
arm-none-eabi-gcc ...
../Sources/host_if/tab_if.c:637: undefined reference to `ASSERT_WARNING_637'
collect2: error: ld returned 1 exit status
make: *** [ntank_app.elf] Error 1
//

答案 10 :(得分:0)

这适用于某些旧版gcc。抱歉,我忘记了它的版本:

#define _cat(x, y) x##y

#define _sassert(exp, ln)\
extern char _cat(SASSERT_, ln)[1]; \
extern char _cat(SASSERT_, ln)[exp ? 1 : 2]

#define sassert(exp) _sassert((exp), __LINE__)

//
sassert(1 == 2);

//
#148 declaration is incompatible with "char SASSERT_134[1]" (declared at line 134)  main.c  /test/source/controller line 134    C/C++ Problem

答案 11 :(得分:0)

在Perl中,特别是perl.h line 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

如果static_assert可用(来自<assert.h>),则使用它。否则,如果条件为假,则声明一个负大小的位字段,这将导致编译失败。

STMT_START / STMT_END是分别扩展为do / while (0)的宏。