在C中静态断言
在C(而不是C ++)中实现编译时静态断言的最好方法是什么,特别强调GCC?
C-1x添加了_Static_assert关键字。
这似乎是在gcc-4.6中实现的:
_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */
第一个槽需要是一个整数常量expression式。 第二个插槽是一个可以很长的常量string( _Static_assert(0, L"assertion of doom!")
)。
我应该注意到,这也是在最近版本的铿锵实施。
这在函数和非函数的作用域(但不是在结构,工会内)。
#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); }
-
如果编译时间断言不能匹配,则GCC
sas.c:4: error: size of array 'static_assertion_this_should_be_true' is negative
生成一个几乎可理解的消息sas.c:4: error: size of array 'static_assertion_this_should_be_true' is negative
-
该macros可以或应该被改变来为typedef生成一个唯一的名字(例如,在
static_assert_...
name的末尾连接__LINE__
) -
而不是一个三元,这也可以使用
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1]
即使在生锈的旧版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(我猜)介绍了“错误”和“警告”function属性。 如果通过死代码消除(或其他措施)无法消除具有该属性的函数调用,则会生成错误或警告。 这可以用来使用户定义的故障描述使编译时间断言。 它仍然可以决定如何在命名空间范围内使用它们,而不必使用虚拟函数:
#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
CL
我知道这个问题明确提到了gcc,但为了完整起见,这里是微软编译器的一个调整。
使用负数大小的数组typedef不能说服CL吐出一个体面的错误。 它只是说error C2118: negative subscript
。 零宽度位域在这方面更好。 由于这涉及到typedeffing结构,我们确实需要使用唯一的types名称。 __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':命名位字段不能有零宽度
海湾合作委员会也给了一个可理解的消息:
错误:零位宽度为'static_assertion_failed_use_another_compiler_luke'
维基百科 :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;} COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
对于那些想要一些真正的基本和可移植的东西,但没有访问C ++ 11function的人,我只写了这个东西。
通常使用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; }
说明:
首先它检查你是否有真正的断言,如果可用,你肯定会使用它。
如果你不这样做的话,你可以通过获得预测来分解它。 这有两件事。
如果它是零,id est,断言失败,它将导致被零除错误(该algorithm是强制的,因为它试图声明一个数组)。
如果不为零,则将数组大小标准化为1
。 所以如果断言通过了,你不希望它失败,因为你的谓词评估为-1
(无效),或者是232442
(大量浪费空间,IDK如果它将被优化)。
对于STATIC_ASSERT
它被包裹在大括号中,这使得它成为一个块,其范围variablesassert
,这意味着你可以写很多次。
它也把它抛弃,这是一个已知的方式来摆脱unused variable
警告。
对于GLOBAL_STATIC_ASSERT
,它不是在代码块中,而是生成一个名称空间。 命名空间被允许在函数之外。 如果您多次使用此定义,则需要unique
标识符来停止任何冲突的定义。
在GCC和VS'12 C ++上为我工作
经典的方法是使用数组:
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
它的工作原理是因为如果断言是真的,数组的大小为1,它是有效的,但如果它是假的大小-1给出编译错误。
大多数编译器将显示variables的名称,并指向代码的右边部分,您可以在这里留下关于断言的最终评论。
这工作,与“删除未使用的”选项设置。 我可以使用一个全局函数来检查全局参数。
// #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 //
如果在__LINE__
使用STATIC_ASSERT()macros,可以通过包含__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]