C编译器断言 - 如何实现?

Nic*_*ckB 41 c compiler-construction assertions

我想在错误的情况下实现一个"断言"来阻止编译,而不是在运行时失败.

我目前有一个这样的定义,它工作得很好,但它增加了二进制文件的大小.

#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;}
Run Code Online (Sandbox Code Playgroud)

示例代码(无法编译).

#define DEFINE_A 1
#define DEFINE_B 1
MY_COMPILER_ASSERT(DEFINE_A == DEFINE_B);
Run Code Online (Sandbox Code Playgroud)

我如何实现它,以便它不生成任何代码(为了最小化生成的二进制文件的大小)?

RBe*_*eig 42

纯标准C中的编译时断言是可能的,并且一些预处理器技巧使其使用看起来像运行时的​​使用一样干净assert().

关键技巧是找到一个可以在编译时进行评估的结构,并且可能会导致某些值出错.一个答案是数组的声明不能具有负大小.使用typedef可防止成功时分配空间,并在失败时保留错误.

错误消息本身将隐式引用负大小的声明(GCC称"数组foo的大小为负"),因此您应该为数组类型选择一个名称,该名称暗示此错误确实是断言检查.

要处理的另一个问题是,只能typedef在任何编译单元中使用特定类型名称.因此,宏必须安排每个用法以获取要声明的唯一类型名称.

我通常的解决方案是要求宏有两个参数.第一个是assert的条件为true,第二个是在幕后声明的类型名称的一部分.plinth的答案提示使用令牌粘贴和__LINE__预定义的宏来形成唯一的名称,可能不需要额外的参数.

不幸的是,如果断言检查在包含文件中,它仍然可能与第二个包含文件中相同行号的检查冲突,或者与主源文件中的该行号冲突.我们可以通过使用宏来解决这个问题__FILE__,但它被定义为一个字符串常量,并且没有预处理器技巧可以将字符串常量转换回标识符名称的一部分; 更不用说合法文件名可以包含不是标识符合法部分的字符.

所以,我建议使用以下代码片段:

/** A compile time assertion check.
 *
 *  Validate at compile time that the predicate is true without
 *  generating code. This can be used at any point in a source file
 *  where typedef is legal.
 *
 *  On success, compilation proceeds normally.
 *
 *  On failure, attempts to typedef an array type of negative size. The
 *  offending line will look like
 *      typedef assertion_failed_file_h_42[-1]
 *  where file is the content of the second parameter which should
 *  typically be related in some obvious way to the containing file
 *  name, 42 is the line number in the file on which the assertion
 *  appears, and -1 is the result of a calculation based on the
 *  predicate failing.
 *
 *  \param predicate The predicate to test. It must evaluate to
 *  something that can be coerced to a normal C boolean.
 *
 *  \param file A sequence of legal identifier characters that should
 *  uniquely identify the source file in which this condition appears.
 */
#define CASSERT(predicate, file) _impl_CASSERT_LINE(predicate,__LINE__,file)

#define _impl_PASTE(a,b) a##b
#define _impl_CASSERT_LINE(predicate, line, file) \
    typedef char _impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1];
Run Code Online (Sandbox Code Playgroud)

典型用法可能是这样的:

#include "CAssert.h"
...
struct foo { 
    ...  /* 76 bytes of members */
};
CASSERT(sizeof(struct foo) == 76, demo_c);
Run Code Online (Sandbox Code Playgroud)

在GCC中,断言失败看起来像:

$ gcc -c demo.c
demo.c:32: error: size of array `assertion_failed_demo_c_32' is negative
$

  • 如果您不关心可移植性,在 GCC 中 \_\_COUNTER\_\_ 可用于提供唯一标识符以粘贴到 typedef 名称上。它是最近添加的(4.3) (3认同)
  • 不幸的是,这种方法现在会触发 GCC 关于未使用的 typedef 的警告。 (3认同)
  • @HannesLandeholm 如果我自己发布它,我通常会申请麻省理工学院的许可证。在此发布,无需任何进一步讨论,可以公平地假设用户内容已获得许可的一般声明为 [CC-BY-SA](http://creativecommons.org/licenses/by-sa/3.0/)适用。为此,您只需将“CC-BY-SA 3.0 from http://stackoverflow.com/a/809465/68204”添加到注释块即可。就我个人而言,我不喜欢对代码施加“相同方式共享”限制,但此处的归属和链接始终是合适的。 (2认同)

Ste*_*eel 7

以下COMPILER_VERIFY(exp)宏工作得相当好.

// combine arguments (after expanding arguments)
#define GLUE(a,b) __GLUE(a,b)
#define __GLUE(a,b) a ## b

#define CVERIFY(expr, msg) typedef char GLUE (compiler_verify_, msg) [(expr) ? (+1) : (-1)]

#define COMPILER_VERIFY(exp) CVERIFY (exp, __LINE__)

它适用于C和C++,可以在任何允许使用typedef的地方使用.如果表达式为true,则为1 char的数组生成typedef(这是无害的).如果表达式为false,则为-1个字符的数组生成typedef,这通常会导致错误消息.作为一个arugment给出的表达式可以是任何评估为编译时常量的表达式(因此涉及sizeof()的表达式可以正常工作).这使它比它更灵活

#if (expr)
#error
#endif

您可以将其限制为可由预处理器评估的表达式.

  • 自从我发布这个答案以来,十年来事情发生了变化。如果您的编译器支持 C11,您可以使用现在已成为该语言一部分的 _Static_assert。 (2认同)

Dyl*_*lan 6

正如 Leander 所说,静态断言正在被添加到 C++11 中,而且现在已经添加了。

static_assert(exp, message)

例如

#include "myfile.hpp"

static_assert(sizeof(MyClass) == 16, "MyClass is not 16 bytes!")

void doStuff(MyClass object) { }
Run Code Online (Sandbox Code Playgroud)

请参阅cppreference 页面

  • 问题是关于 C 而不是 C++。 (10认同)
  • C11 提供了类似名称的“_Static_assert”(请参阅​​ /sf/answers/510113901/)。此外,“assert.h”包含映射到它的“static_assert”宏(https://en.cppreference.com/w/c/error/static_assert) (3认同)

Ste*_*sky 5

使用“#error”是一个有效的预处理器定义,它会导致大多数编译器上的编译停止。例如,您可以这样做,以防止在调试中进行编译:


#ifdef DEBUG
#error Please don't compile now
#endif
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,这会在预处理器级别中止,因此它无法处理诸如“assert(sizeof(long) == sizeof(void *))”之类的事情。 (7认同)