在 C(不是 C++)中实现编译时静态断言的最佳方法是什么,特别强调 GCC?
13 回答
C11 标准添加了_Static_assert
关键字。
_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */
第一个槽需要是一个整数常量表达式。第二个槽是一个常量字符串文字,它可以是长的 ( _Static_assert(0, L"assertion of doom!")
)。
我应该注意到,这也在最新版本的 clang 中实现。
这适用于函数和非函数范围(但不适用于结构、联合)。
#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]
它而不是三进制,即使在生锈的 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
分类
我知道这个问题明确提到了 gcc,但为了完整起见,这里是对 Microsoft 编译器的调整。
使用负大小的数组 typedef 并不能说服cl吐出一个像样的错误。它只是说error C2118: negative subscript
。在这方面,零宽度位域的表现更好。由于这涉及对结构进行类型定义,因此我们确实需要使用唯一的类型名称。__LINE__
不削减芥末 - 可能COMPILE_TIME_ASSERT()
在头文件和源文件的同一行中有 a ,并且您的编译将中断。__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”的宽度为零</p>
因为:
_Static_assert()
现在在 gcc 中为所有 C 版本定义,并且static_assert()
在 C++11 及更高版本中定义(在 C11 中也是,作为方便的宏_Static_assert()
;参见此处和此处)
因此,以下简单的宏STATIC_ASSERT()
适用于:
- C++:
- C++11 (
g++ -std=c++11
) 或更高版本
- C++11 (
- 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"
例子:
使用 gcc 4.8.4 在 Ubuntu 中测试:
示例 1:良好的gcc
输出(即:STATIC_ASSERT()
代码有效,但条件为假,导致编译时断言):
$ gcc -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: In function 'main'<br /> static_assert.c:78:38: error: static assertion failed: "(1 > 2) failed"
#定义 STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")
^
static_assert.c:88:5: 注意:在宏'STATIC_ASSERT'的扩展中<br /> 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()'<br /> static_assert.c:74:32: 错误:静态断言失败: (1 > 2) failed
#define _Static_assert static_assert /*static_assert
是 C++11 或更高版本的一部分 */
^
static_assert.c:78:38: 注意:在宏 '_Static_assert' 的扩展中<br /> #define STATIC_ASSERT( test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")
^
static_assert.c:88:5: 注意:在宏'STATIC_ASSERT'的扩展中<br /> 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()'<br /> static_assert.c:78:99: 错误: 'static_assert' 未在此范围内声明
#define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "( " #test_for_true ") failed")
^
static_assert.c:88:5: 注意:在宏'STATIC_ASSERT'的扩展中<br /> 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;
}
有关的:
来自维基百科:
#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
于在编译时评估的。它不适用于(即编译将失败)基于内存中值的条件,例如分配给变量的值。
经典的方法是使用数组:
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
它之所以有效,是因为如果断言为真,则数组的大小为 1 并且有效,但如果为假,则 -1 的大小会产生编译错误。
大多数编译器会显示变量的名称并指向代码的正确部分,您可以在其中留下有关断言的最终注释。
__LINE__
如果将STATIC_ASSERT() 宏与__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]
从 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)
。
对于那些想要一些真正基本和可移植但无法访问 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++ 上为我工作
这适用于“删除未使用的”选项集。我可以使用一个全局函数来检查全局参数。
//
#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
//
对于 C11 之前的 C 版本,可以构建自己的静态断言。以下是在旧版本的 GCC 上测试的。
当然,如果您可以使用 C11,那么最好#include <assert.h>
使用static_assert
.
/** @file
* STATIC_ASSERT allows you to do compile time assertions at file scope or in a function.
* @param expr: a boolean expression that is valid at compile time.
* @param msg: a "message" that must also be a valid identifier, i.e. message_with_underscores
*/
#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__ */
#define STATIC_ASSERT_ARRAY_LEN(array, len) \
STATIC_ASSERT(sizeof(array)/sizeof(array[0]) == len, array##_wrong_size);
#endif // STATIC_ASSERT_H
这个想法与 Hashbrown 的回答基本相同,除了我有数组助手和 gnuc 的特殊情况。
这适用于一些旧的 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