C中的静态断言

问题描述 投票:73回答:12

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

c gcc assert compile-time static-assert
12个回答
75
投票

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中实现的。


0
投票

这适用于“删除未使用”选项集。我可以使用一个全局函数来检查全局参数。

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

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

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>),则使用它。否则,如果条件为false,则声明具有负大小的位字段,这会导致编译失败。

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


81
投票

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

#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. 可以或应该更改宏以生成typedef的唯一名称(即在__LINE__名称末尾连接static_assert_...
  3. 而不是三元,这可以用作#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1],它甚至可以在生锈的olde cc65(用于6502 cpu)编译器上工作。

更新:为了完整起见,这里是__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

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'


4
投票

来自Wikipedia

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

COMPILE_TIME_ASSERT( BOOLEAN CONDITION );

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。它将无法使用(即编译将失败)条件基于内存中的值,例如分配给变量的值。


2
投票

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

例如 :

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

1
投票

经典的方法是使用数组:

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

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

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


1
投票

Because:

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

The following simple macro for STATIC_ASSERT() therefore works in:

  1. C ++: C ++ 11(g++ -std=c++11)或更高版本
  2. C: gcc -std=c90 gcc -std=c99 gcc -std=c11 gcc(没有指定标准)

定义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" 

Examples:

使用gcc 4.8.4在Ubuntu中测试:

示例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 ++ 11之前使用的C ++版本):

$ 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); ^

Full test results here:

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

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;
}

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


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

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.