在C(不是C ++)中,特别是在GCC上,实现编译时静态断言的最佳方法是什么?
Answers:
C11标准添加了_Static_assert
关键字。
这是因为GCC-4.6实现的:
_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */
第一个时隙必须是整数常量表达式。第二个插槽是常量字符串文字,可以是long(_Static_assert(0, L"assertion of doom!")
)。
我应该注意,这在clang的最新版本中也已实现。
error: expected declaration specifiers or '...' before 'sizeof'
上线了static_assert( sizeof(int) == sizeof(long int), "Error!);
(顺便说一句,我使用的是C而不是C ++)
_Static_assert( sizeof(int) == sizeof(long int), "Error!");
在我的机器上,我得到了错误。
error: expected declaration specifiers or '...' before 'sizeof'
AND error: expected declaration specifiers or '...' before string constant
(他指的是"Error!"
字符串)(另:我正在使用-std = c11进行编译。将声明放入函数中时,所有方法都工作正常(失败并按预期成功))
_Static_assert
而不是C ++ ish static_assert
。您需要`#include <assert.h>来获取static_assert宏。
这在功能和非功能范围内有效(但在结构,联合内部无效)。
#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
可以或应该更改宏以为typedef生成唯一名称(即__LINE__
,在static_assert_...
名称末尾串联)
代替三进制,也可以使用#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(我猜想)引入了“错误”和“警告”功能属性。如果无法通过消除无效代码(或其他措施)来消除对具有该属性的函数的调用,则会生成错误或警告。这可用于根据用户定义的故障描述进行编译时声明。还需要确定如何在不使用伪函数的情况下将它们用于命名空间范围:
#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
-Og
)通常足以满足要求,并且不应干扰调试。如果未定义__OPTIMIZE__
(和__GNUC__
),则可以考虑使静态断言成为no-op或运行时断言。
__LINE__
gcc 4.1.1中的版本的方法... ...当两个不同的标头恰好在同一编号行上有一个标头时,有时会感到烦恼!
我知道这个问题明确提到了gcc,但是为了完整起见,这是针对Microsoft编译器的一项调整。
使用负大小的数组typedef不能说服cl吐出体面的错误。它只是说error C2118: negative subscript
。在这方面,零宽度的位域效果更好。由于这涉及到对结构的typedeffing,因此我们确实需要使用唯一的类型名。__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”的宽度为零
从维基百科:
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
我会 不建议通过以下方式使用解决方案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
在编译时与之一起使用。它不能基于内存中的值(例如分配给变量的值)使用(即,编译将失败)。
如果将STATIC_ASSERT()宏与一起使用__LINE__
,则可以通过在.c文件中的条目与头文件中的其他条目之间避免行号冲突__INCLUDE_LEVEL__
。
例如 :
/* 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]
经典方法是使用数组:
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
之所以起作用,是因为如果断言为true,则数组的大小为1并且有效,但是如果为false,则大小为-1会产生编译错误。
大多数编译器将显示变量的名称,并指向代码的右侧,您可以在其中留下有关断言的最终注释。
#define STATIC_ASSERT()
类型宏中,并使用泛型示例提供更多的泛型示例和示例编译器输出,STATIC_ASSERT()
将会给您带来更多的赞誉,并使这种技术更有意义。
在Perl中,特别是perl.h
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)
。
_Static_assert()
现在在gcc中为所有C版本定义了,并且 static_assert()
在C ++ 11及更高版本中定义STATIC_ASSERT()
适用于:g++ -std=c++11
)或更高版本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"
在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()
代码有效,但条件为假,导致编译时断言):
$ 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)的扩展中;
^
/*
static_assert.c
- test static asserts in C and C++ using gcc compiler
Gabriel Staples
4 Mar. 2019
To be posted in:
1. /programming/987684/does-gcc-have-a-built-in-compile-time-assert/987756#987756
2. /programming/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;
}
static_assert
宏时,为什么如此复杂assert.h
?
static_assert()
在C语言中根本无法使用。另请参见:en.cppreference.com/w/cpp/language/static_assert-它显示static_assert
存在“(自C ++ 11起)”。我的回答的妙处在于,它可以在gcc的C90和更高版本以及任何C ++ 11和更高版本中运行,而不仅仅是在C ++ 11和更高版本中(如)static_assert()
。另外,我的答案有什么复杂之处?只有几#define
秒钟。
static_assert
从C11开始在C中定义。它是一个扩展为的宏_Static_assert
。zh.cppreference.com/w/c/error/static_assert。另外,与您的答案形成对比的_Static_assert
在gcc的c99和c90中不可用(仅在gnu99和gnu90中)。这符合标准。基本上,您需要做很多额外的工作,只有在使用gnu90和gnu99进行编译时才会带来好处,并且使实际用例很小。
对于那些想要真正基本且可移植但又无法访问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
冰糖并单独对其进行断言。这有两件事。
如果为零,则表示断言失败,它将导致被零除的错误(该算术是强制的,因为它试图声明一个数组)。
如果不为零,则将数组大小标准化为1
。因此,如果断言通过,则无论如何您都不希望它失败,因为您的谓词评估为-1
(无效)或为232442
(大量空间浪费,如果可以优化则IDK)。
因为STATIC_ASSERT
用大括号括起来,这使其成为一个块,从而对变量进行范围划分assert
,这意味着您可以多次编写。
它还将其强制转换为void
,这是摆脱unused variable
警告的一种已知方法。
对于GLOBAL_STATIC_ASSERT
,它不是在代码块中,而是生成一个名称空间。允许在函数外部使用命名空间。unique
如果多次使用该标识符,则必须使用标识符来停止任何冲突的定义。
在GCC和VS'12 C ++上为我工作
这可以通过设置“删除未使用的”选项来实现。我可能会使用一个全局函数来检查全局参数。
//
#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
//
这适用于一些旧的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
_Static_assert
是C11标准的一部分,任何支持C11的编译器都将拥有它。