C 语言中的静态断言

如何在 C (而不是 C + +)中实现编译时静态断言,并特别强调 GCC?

108324 次浏览

来自 Wikipedia:

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


COMPILE_TIME_ASSERT( BOOLEAN CONDITION );

这在函数和非函数作用域(但不在结构、联合中)中起作用。

#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. 如果编译时断言无法匹配,那么 GCCsas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative将生成一条几乎可以理解的消息

  2. 可以或应该更改宏,以便为 typedef 生成唯一的名称(即,在 static_assert_...名称的末尾连接 __LINE__)

  3. 与三进制编译器不同,它也可以使用 #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(我猜)引入了“ error”和“ police”函数属性。如果对具有该属性的函数的调用无法通过死码删除(或其他度量)消除,则会生成错误或警告。这可用于使用用户定义的故障描述进行编译时断言。如何在不使用虚函数的情况下在名称空间范围内使用它们仍有待确定:

#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 并不能说服 克莱尔吐出一个不错的错误。上面只写了 error C2118: negative subscript。零宽度位字段在这方面表现得更好。因为这涉及到对结构进行类型定义,所以我们确实需要使用唯一的类型名称。__LINE__没有切断芥末 & mdash; 它可能有一个 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 _ asser_ error _ use _ another _ Editor _ luke’: 命名位字段不能为零宽度

海湾合作委员会还发出了一个明确的信息:

错误: 位字段‘ static _ 断言 _ false _ use _ another _ Editor _ luke’的零宽度

C11标准添加了 _Static_assert关键字。

这里是 自 gcc-4.6起实施:

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

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

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

对于那些想要一些真正基本和可移植的东西,但又不能访问 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;
}

Explanation:
首先,它检查您是否有真正的断言,如果它可用,您肯定希望使用它。
如果你不这样做,它会得到你的 predate,然后把它除以它自己,这样做有两个目的。
如果为0,id est,则断言失败,它将导致除数为0的错误(这个算法是强制的,因为它试图声明一个数组)。
如果不是零,则将数组大小标准化为 1。因此,如果断言通过,您不会希望它失败,因为您的断言计算为 -1(无效) ,或者是 232442(大量的空间浪费,IDK,如果它将被优化)。
对于 STATIC_ASSERT,它是用大括号包装的,这使它成为一个块,其作用域为变量 assert,这意味着您可以多次编写它。
它还将其强制转换为 void,这是一种已知的消除 unused variable警告的方法。
对于 GLOBAL_STATIC_ASSERT,它不在代码块中,而是生成一个名称空间。允许在函数之外使用名称空间。如果多次使用此标识符,则需要 unique标识符来停止任何冲突的定义。


在海湾合作委员会和 VS’12C + + 上为我工作

经典的方法是使用数组:

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

它之所以能够工作,是因为如果断言为真,则数组的大小为1,且该数组是有效的,但如果为假,则 -1的大小将导致编译错误。

Most compilers will show the name of the variable and point to the right part of the code where you can leave eventual comments about the assertion.

这工作,与“删除未使用”选项设置。我可以使用一个全局函数来检查全局参数。

//
#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 ()宏,则可以避免。通过包含 __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]

This worked for some old gcc. Sorry that I forgot what version it was:

#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

我建议使用 typedef的解决方案:

// Do NOT do this
#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上)这样做:

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

由于使用了 static关键字,数组将在编译时定义。请注意,这个断言将只与在编译时计算的 COND一起工作。它不能处理基于内存中值的条件(例如赋给变量的值)(即编译将失败)。

This answer vastly improved Apr. 17 2022, as an Easter gift. I believe it to be the most thorough and complete answer here, since I have produced 3 separate solutions of varying complexities to cover different versions of C and C++, and since the last version I present covers 所有 versions of C and C++, which was initially a seemingly impossible feat.

您可以在我的文件 < strong > static _ asser_ for _ all _ version _ of _ c _ and _ cpp.c 中查看和测试下面的代码以获得 C 和 C + + 的所有版本。

请注意,C++ style comments are not allowed in ISO C90,所以我的代码示例必须只使用 C 样式的注释(/* */) ,而不是 C + + 样式的 //注释,以便我的代码也能够在 -std=c90中编译。

获取 STATIC_ASSERT(test_for_true)的快速摘要(TLDR) :

如果你想让一个快速而又超级简单的宏在任何版本的 C (使用 gcc 编译时)或任何版本的 C + + 作为 C + + 11或更高版本中工作,请看下一节的底部: “ C 和 C + + 中可用的静态断言声明的摘要”。为方便起见,下面是复制和粘贴的宏:

  1. [目前为止最简单的选择! ] 只有 C11或更高版本 < em > 和 只有 C + + 11或更高版本STATIC_ASSERT(test_for_true):
    #include <assert.h>
    #define STATIC_ASSERT(test_for_true) \
    static_assert((test_for_true), "(" #test_for_true ") failed")
    
  2. 或者[我的偏好] ,STATIC_ASSERT(test_for_true)对于 C 的任何 版本(在使用 gcc 编译器时作为 gcc 扩展) ,包括 C90、 C99、 C11、 C17等,对于 C + + 11或更高版本,< em > 和 (不能处理 C + + 的旧版本) :
    #ifdef __cplusplus
    #ifndef _Static_assert
    #define _Static_assert static_assert
    #endif
    #endif
    #define STATIC_ASSERT(test_for_true) \
    _Static_assert((test_for_true), "(" #test_for_true ") failed")
    

如果您希望单个 STATIC_ASSERT宏能够在 C 和 C + + 的 所有版本中工作,我将在下面以“我的最终版本”开头的章节中介绍它。您可以在底部的“测试摘要”部分看到我用来测试它的构建命令和语言设置。让一个静态断言在 pre-C++11中工作,比如 C + + 98,C + + 03,等等,是困难的部分!下面的 _Static_assert_hack宏处理那些早期版本的 C + + 。为了方便起见,下面是处理所有版本的 C 和 C + + 的完整代码块,删除了大部分注释:

STATIC_ASSERT(test_for_true)呼叫 任何版本的 C 和任何版本的 C + + :

// See: https://stackoverflow.com/a/54993033/4561887


#define CONCAT_(prefix, suffix) prefix##suffix
#define CONCAT(prefix, suffix) CONCAT_(prefix, suffix)
#define MAKE_UNIQUE_VARIABLE_NAME(prefix) CONCAT(prefix##_, __LINE__)


/* Static assert hack required for **pre-C++11**, such as C++98, C++03, etc. */
/* - It works only with C++, NOT with C! */
#define _Static_assert_hack(expression, message) \
struct MAKE_UNIQUE_VARIABLE_NAME(static_assertion_failed) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wunused-local-typedefs\"") \
typedef char static_assertion_failed[(expression) ? 1 : -1]; \
_Pragma("GCC diagnostic pop") \
}


/* For C++ only: */
/* See: https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html */
#ifdef __cplusplus
#if __cplusplus < 201103L
/* for pre-C++11 */
#ifndef _Static_assert
#define _Static_assert _Static_assert_hack
#endif
#else
/* for C++11 or later */
#ifndef _Static_assert
#define _Static_assert static_assert
#endif
#endif
#endif


/* For C **and** C++: */
#define STATIC_ASSERT(test_for_true) \
_Static_assert((test_for_true), "(" #test_for_true ") failed")

C 和 C + + 中可用的静态断言声明摘要:

要知道:

  1. C 语言: _Static_assert(expression, message)C11或更晚中可用。
    1. 根据上面的 cppreference.com 社区 wiki 链接,为了与 C + + 11中的命名相匹配,static_assert也可以作为一个方便的宏来使用。因此,要在 C11或更晚中将类似 C + + 的 static_assert作为宏,还应该使用 #include <assert.h>
    2. _Static_assert(expression)(即: 不包括 message部分)也可以从 C23或更高开始使用。
  2. C + + 语言: static_assert(expression, message)C + + 11或更高版本中可用。
    1. static_assert(expression)(即: 没有 message部分)也可在 C + + 17或更高版本
  3. gcc compiler:
    1. Gcc 编译器版本4.6及更高版本中,支持 _Static_assert作为 所有 版本的 C 的 gcc 扩展名,包括 c90、 c99、 c11、 c17等。
      1. 而且,根据 C11标准,如上所述,如果您也使用 #include <assert.h>,那么 static_assert可以作为宏提供给 C11或更高版本的 _Static_assert
    2. G + + 编译器版本4.3及更高版本开始,支持将 static_assert作为 C + + 11或更高版本的关键字。你不需要像在 C 中那样在 C + + 中使用 #include <assert.h>来获得这种格式。
    3. 海湾合作委员会资料来源: https://www.gnu.org/software/gnulib/manual/html_node/assert_002eh.html(重点补充) :

    更老的平台根本不支持 static_assert_Static_assert。例如,4.6之前的 GCC 版本不支持 _Static_assertG++ versions before 4.3 do not support static_assert,它们是由 C11和 C + + 11标准化的。

    C _Static_assert和 C + + static_assert是可以在不包括 <assert.h>的情况下使用的关键字。Gnulib 替代品是需要包含 <assert.h>的宏。

    1. 另见: https://gcc.gnu.org/wiki/C11Status——我从 主要答案得到了这个链接。

我喜欢编写一个 STATIC_ASSERT包装器宏,将参数减少到1并自动生成 message参数,这样我就可以执行 STATIC_ASSERT(expression)而不是 STATIC_ASSERT(expression, message)。以下是如何轻松做到这一点:

  1. 对于 只有 C11或更高版本 < em > 和 只有 C + + 11或更高版本:
    #include <assert.h>
    #define STATIC_ASSERT(test_for_true) \
    static_assert((test_for_true), "(" #test_for_true ") failed")
    
  2. 或者[我的偏好] ,对于 C 的任何 版本(在使用 gcc 编译器时作为 gcc 扩展) ,包括 C90、 C99、 C11、 C17等,对于 C + + 11或更高版本,< em > 和 (不能处理 C + + 的旧版本) :
    #ifdef __cplusplus
    #ifndef _Static_assert
    #define _Static_assert static_assert
    #endif
    #endif
    #define STATIC_ASSERT(test_for_true) \
    _Static_assert((test_for_true), "(" #test_for_true ") failed")
    
  3. 对于 比 C + + 11还老的 C + + 的版本,您必须使用 hack 来获得函数式静态断言。使用下面展示的相当复杂的 Pre-C + + 11静态断言黑客技术,或者,(甚至更好!)升级到 C + + 11或更高版本。

在我的 < strong > static _ asser_ for _ all _ version _ of _ c _ and _ cpp.c 中测试上面的代码片段。

对于非 gcc C11前期Pre-C + + 11,静态断言 hacks

1/2. 只适用于 C (例如: 适用于非 gcc C11前期)

对于 gccC11前期,gcc 已经定义了 _Static_assert(expression, message),这非常好。所以,只要使用它就可以了,就像上面描述的那样!但是,如果不使用 gcc 编译器会怎么样呢?你能做什么?

Well, I noticed something really interesting. If I use _Static_assert(1 > 2, "this should fail"); in C90 with gcc, using this build command:

gcc -Wall -Wextra -Werror -O3 -std=c90 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a

I get this compile-time error for that failed _Static_assert. This is a super weird error! This is not an accidental build error though, this is the 静态断言失效错误, because they are also using a hack for this version of C to get compile-time static assertions!

In file included from /usr/include/features.h:461,
from /usr/include/x86_64-linux-gnu/bits/libc-header-start.h:33,
from /usr/include/stdint.h:26,
from /usr/lib/gcc/x86_64-linux-gnu/9/include/stdint.h:9,
from static_assert_for_all_versions_of_c_and_cpp.c:73:
static_assert_for_all_versions_of_c_and_cpp.c: In function ‘main’:
static_assert_for_all_versions_of_c_and_cpp.c:224:5: error: negative width in bit-field ‘__error_if_negative’
224 |     _Static_assert(1 > 2, "this should fail");
|     ^~~~~~~~~~~~~~

如果我在 GitHub 上找到 gcc 源代码镜像(https://github.com/gcc-mirror/gcc) ,克隆回购文件,然后使用 grep 或 ripgrep 搜索 __error_if_negative,我只在一个地方找到一个结果,这里: https://github.com/gcc-mirror/gcc/blob/master/libgcc/soft-fp/soft-fp.h#L69-L71:

# define _FP_STATIC_ASSERT(expr, msg)                   \
extern int (*__Static_assert_function (void))             \
[!!sizeof (struct { int __error_if_negative: (expr) ? 2 : -1; })]

这是一个静态断言黑客,你可以借鉴和使用在非 gcc 版本的前 C11C!

只要用 _Static_assert代替 _FP_STATIC_ASSERT,像这样:

# define _Static_assert(expr, msg)                   \
extern int (*__Static_assert_function (void))             \
[!!sizeof (struct { int __error_if_negative: (expr) ? 2 : -1; })]

上面提到的使用 _Static_assert的注意事项:

  1. 只有在 C 中工作,not在 C + + 中工作!
  2. 它在 ISO C 中的结构或联合中执行 没有工作——例如: 当您使用 -std=c90-std=c99等时。
    1. 我相信,如果您使用 gnu C 语言,比如 -std=gnu90-std=gnu99,那么 是的是可以工作的。
    2. 如果您尝试在联合中使用它,或者像下面这样构造:
      typedef union data_u
      {
      data_t data;
      uint8_t bytes[sizeof(data_t)];
      
      
      _Static_assert(2 > 1, "this should pass");
      _Static_assert(5 > 4, "this should pass");
      } data_union_t;
      
      然后你会看到这个关于 expected specifier-qualifier-list before ‘extern’的超级神秘的错误。这是 没有,因为静态断言表达式失败(为 false) ,而是因为此用例中的 静态断言 hack 是 < strong > break 。注意,在上面的代码中使用了 extern,因此,它出现在错误中:
      eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=c90 static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
      In file included from /usr/include/features.h:461,
      from /usr/include/x86_64-linux-gnu/bits/libc-header-start.h:33,
      from /usr/include/stdint.h:26,
      from /usr/lib/gcc/x86_64-linux-gnu/9/include/stdint.h:9,
      from static_assert_for_all_versions_of_c_and_cpp.c:73:
      static_assert_for_all_versions_of_c_and_cpp.c:193:5: error: expected specifier-qualifier-list before ‘extern’
      193 |     _Static_assert(2 > 1, "this should pass");
      |     ^~~~~~~~~~~~~~
      

2/2. 仅适用于 C + + (例如: 适用于 Pre-C + + 11)

我发现在 C + + 11之前的 C + + 环境下,要让一个漂亮的静态断言黑客工作起来非常困难,但是我有一个工作起来了!这是相当艺术的工作,但它似乎工作,工作良好,是健壮的。它也像 C + + 11的 static_assert一样在结构和联合中工作!就是这个。你可以在我的 < strong > static _ asser_ for _ all _ version _ of _ c _ and _ cpp.c 里测试它:

#define CONCAT_(prefix, suffix) prefix##suffix
#define CONCAT(prefix, suffix) CONCAT_(prefix, suffix)
#define MAKE_UNIQUE_VARIABLE_NAME(prefix) CONCAT(prefix##_, __LINE__)


/* Static assert hack required for **pre-C++11**, such as C++98, C++03, etc. */
/* - It works only with C++, NOT with C! */
#define _Static_assert_hack(expression, message) \
struct MAKE_UNIQUE_VARIABLE_NAME(static_assertion_failed) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wunused-local-typedefs\"") \
typedef char static_assertion_failed[(expression) ? 1 : -1]; \
_Pragma("GCC diagnostic pop") \
}

我的最终版本: 一个单独的 STATIC_ASSERT(),当使用 gcc 编译时,它可以与 C 的 所有版本和 C + + 的 所有版本一起工作

只要稍作调整就可以更改使用的样式和时间,下面的代码也可以用于非 gcc 编译器上的 C 和 C + + 的 任何版本。

在编写本文时,我希望它能够在用 gcc/g + + 编译器 或者和 LLVM clang 编译器编译时,适用于 C 和 gnu C 的 所有版本和 C + + 和 gnu + + 的 所有版本。

下面是最终版本: 一个静态断言来处理任何版本的 C 或 C + + ! :

/* --------------------------------- START ---------------------------------- */
/* OR [BEST], for **any version of C OR C++**: */


/* See: https://stackoverflow.com/a/71899854/4561887 */
#define CONCAT_(prefix, suffix) prefix##suffix
/* Concatenate `prefix, suffix` into `prefixsuffix` */
#define CONCAT(prefix, suffix) CONCAT_(prefix, suffix)
/* Make a unique variable name containing the line number at the end of the */
/* name. Ex: `uint64_t MAKE_UNIQUE_VARIABLE_NAME(counter) = 0;` would */
/* produce `uint64_t counter_7 = 0` if the call is on line 7! */
#define MAKE_UNIQUE_VARIABLE_NAME(prefix) CONCAT(prefix##_, __LINE__)


/* Static assert hack required for **pre-C++11**, such as C++98, C++03, etc. */
/* - It works only with C++, NOT with C! */
/* See: */
/* 1. [my ans with this] https://stackoverflow.com/a/54993033/4561887 */
/* 1. Info. on `_Pragma()`: https://stackoverflow.com/a/47518775/4561887 */
/* 1. The inspiration for this `typedef char` array hack as a struct  */
/*    definition: https://stackoverflow.com/a/3385694/4561887 */
/* Discard the `message` portion entirely. */
#define _Static_assert_hack(expression, message) \
struct MAKE_UNIQUE_VARIABLE_NAME(static_assertion_failed) \
{ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wunused-local-typedefs\"") \
typedef char static_assertion_failed[(expression) ? 1 : -1]; \
_Pragma("GCC diagnostic pop") \
}


/* For C++ only: */
/* See: https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html */
#ifdef __cplusplus
#if __cplusplus < 201103L
/* for pre-C++11 */
#ifndef _Static_assert
#define _Static_assert _Static_assert_hack
#endif
#else
/* for C++11 or later */
#ifndef _Static_assert
#define _Static_assert static_assert
#endif
#endif
#endif


/* For C **and** C++: */
#define STATIC_ASSERT(test_for_true) \
_Static_assert((test_for_true), "(" #test_for_true ") failed")
/* ---------------------------------- END ----------------------------------- */

帮助我构建这个的 references I used在上面源代码的注释中。下面是从这里复制的可点击链接:

  1. 如何使用宏自动生成包含行号的唯一变量名
    1. 我主要是从 @ Jarod42 here@ 亚当,我是罗森菲尔德中学到这一点的。
  2. 信息。在 _Pragma(): 如何禁用几行代码的 GCC 警告
  3. 这个 typedef char数组作为 struct 的灵感来源 定义:
    1. 回答@Nordic. 主机
    2. Gcc 源代码静态断言黑客技术
  4. Gcc 预定义宏:
    1. Https://gcc.gnu.org/onlinedocs/cpp/standard-predefined-macros.html
    2. Https://gcc.gnu.org/onlinedocs/cpp/common-predefined-macros.html

C + + 03示例静态断言错误输出:

Using the above STATIC_ASSERT in a pre-C++11 use-case, here is some sample code with a static assert which is expected to fail since it is false:

typedef union data_u
{
data_t data;
uint8_t bytes[sizeof(data_t)];


STATIC_ASSERT(2 > 2); /* this should fail */
} data_union_t;

下面是构建命令和失败输出的样子。对于 C + + 中一个失败的静态断言来说,这是一个满口的错误,但是对于这样的黑客攻击来说,这是可以预料的,而上面提到的针对 _Static_assert的 gcc C90黑客攻击也没有好到哪里去:

eRCaGuy_hello_world/c$ g++ -Wall -Wextra -Werror -O3 -std=c++03 static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
static_assert_for_all_versions_of_c_and_cpp.c:129:67: error: narrowing conversion of ‘-1’ from ‘int’ to ‘long unsigned int’ is ill-formed in C++11 [-Werror=narrowing]
129 |         typedef char static_assertion_failed[(expression) ? 1 : -1]; \
|                                                                   ^
static_assert_for_all_versions_of_c_and_cpp.c:139:36: note: in expansion of macro ‘_Static_assert_hack’
139 |             #define _Static_assert _Static_assert_hack
|                                    ^~~~~~~~~~~~~~~~~~~
static_assert_for_all_versions_of_c_and_cpp.c:151:5: note: in expansion of macro ‘_Static_assert’
151 |     _Static_assert((test_for_true), "(" #test_for_true ") failed")
|     ^~~~~~~~~~~~~~
static_assert_for_all_versions_of_c_and_cpp.c:187:5: note: in expansion of macro ‘STATIC_ASSERT’
187 |     STATIC_ASSERT(2 > 2);
|     ^~~~~~~~~~~~~
static_assert_for_all_versions_of_c_and_cpp.c:129:59: error: size ‘-1’ of array ‘static_assertion_failed’ is negative
129 |         typedef char static_assertion_failed[(expression) ? 1 : -1]; \
|                                              ~~~~~~~~~~~~~^~~~~~~~
static_assert_for_all_versions_of_c_and_cpp.c:139:36: note: in expansion of macro ‘_Static_assert_hack’
139 |             #define _Static_assert _Static_assert_hack
|                                    ^~~~~~~~~~~~~~~~~~~
static_assert_for_all_versions_of_c_and_cpp.c:151:5: note: in expansion of macro ‘_Static_assert’
151 |     _Static_assert((test_for_true), "(" #test_for_true ") failed")
|     ^~~~~~~~~~~~~~
static_assert_for_all_versions_of_c_and_cpp.c:187:5: note: in expansion of macro ‘STATIC_ASSERT’
187 |     STATIC_ASSERT(2 > 2);
|     ^~~~~~~~~~~~~
cc1plus: all warnings being treated as errors


测试摘要

See < strong > static _ asser_ for _ all _ version _ of _ c _ and _ cpp.c .

上面介绍的最后一个 STATIC_ASSERT(test_for_true)宏处理的是 C 和 C + + 的 所有版本,它在 Linux Ubuntu 20.04上使用 gcc 编译器版本(gcc --version) 9.4.0(gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0)进行了测试。

下面是对其进行测试和工作的各种构建命令和语言。同样,在这些版本的 所有中,只有允许在结构和联合中使用 STATIC_ASSERT()宏,它们是 -std=c90-std=c99!所有其他选项都支持在任何地方使用 STATIC_ASSERT,包括函数外部、函数内部以及结构和联合内部。

# -------------------
# 1. In C:
# -------------------


gcc -Wall -Wextra -Werror -O3 -std=c90 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
gcc -Wall -Wextra -Werror -O3 -std=c99 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
gcc -Wall -Wextra -Werror -O3 -std=c11 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
gcc -Wall -Wextra -Werror -O3 -std=c17 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a


# gnu C
gcc -Wall -Wextra -Werror -O3 -std=gnu90 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
gcc -Wall -Wextra -Werror -O3 -std=gnu99 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
gcc -Wall -Wextra -Werror -O3 -std=gnu11 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a
# [my default C build cmd I use today]:
gcc -Wall -Wextra -Werror -O3 -std=gnu17 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a -lm && bin/a


# -------------------
# 2. In C++
# -------------------


g++ -Wall -Wextra -Werror -O3 -std=c++98 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
g++ -Wall -Wextra -Werror -O3 -std=c++03 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
g++ -Wall -Wextra -Werror -O3 -std=c++11 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
g++ -Wall -Wextra -Werror -O3 -std=c++17 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
# gnu++
g++ -Wall -Wextra -Werror -O3 -std=gnu++98 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
g++ -Wall -Wextra -Werror -O3 -std=gnu++03 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
g++ -Wall -Wextra -Werror -O3 -std=gnu++11 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a
# [my default C++ build cmd I use today]:
g++ -Wall -Wextra -Werror -O3 -std=gnu++17 \
static_assert_for_all_versions_of_c_and_cpp.c -o bin/a && bin/a

相关阅读:

  1. 使用 static _ assert 检查传递给宏 [我自己的答案]的类型
    1. Https://en.cppreference.com/w/cpp/types/is_same
    2. Https://en.cppreference.com/w/cpp/language/decltype
  2. 使用 static _ assert 检查传递给宏的类型
  3. 如何在 C 中使用静态断言来检查传递给宏的参数的类型

来自 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)的宏。

对于比 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 的答案基本相同,除了我使用了数组助手(array helper)和 gnuc 的一个特殊情况。

资料来源: https://github.com/apache/qpid-dispatch/blob/f2e205c733558102006ed6dd0a44453c9821c80a/include/qpid/dispatch/static_assert.h#L23-L44

简单: 包括 sys/cdefs.h和使用 _Static_assert

I recommend the same approach for C++ and other compilers too.