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

时间:2009-04-30 14:37:54

标签: c compiler-construction assertions

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

我目前有一个这样的定义,效果很好,但会增加二进制文件的大小。

#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;}

示例代码(无法编译)。

#define DEFINE_A 1
#define DEFINE_B 1
MY_COMPILER_ASSERT(DEFINE_A == DEFINE_B);

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

10 个答案:

答案 0 :(得分:40)

纯标准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];

典型用法可能是:

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

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

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

答案 1 :(得分: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

您只能使用预处理器可以评估的表达式。

答案 2 :(得分:4)

如果你的编译器设置了一个像DEBUG或NDEBUG这样的预处理器宏,你可以做这样的事情(否则你可以在Makefile中设置它):

#ifdef DEBUG
#define MY_COMPILER_ASSERT(EXPRESSION)   switch (0) {case 0: case (EXPRESSION):;}
#else
#define MY_COMPILER_ASSERT(EXPRESSION)
#endif

然后,您的编译器仅为调试版本断言。

答案 3 :(得分:4)

我在C中的静态断言中找到的最佳写法是pixelbeat。请注意,静态断言正被添加到C ++ 0X中,并且可能会进入C1X,但这不会有一段时间。我不知道我给出的链接中的宏是否会增加二进制文件的大小。我怀疑他们不会,至少如果你在合理的优化水平上编译,但你的里程可能会有所不同。

答案 4 :(得分:4)

我知道你对C感兴趣,但看看boost的C ++ static_assert。 (顺便说一句,这很可能在C ++ 1x中可用。)

我们已经为C ++做了类似的事情:

#define COMPILER_ASSERT(expr)  enum { ARG_JOIN(CompilerAssertAtLine, __LINE__) = sizeof( char[(expr) ? +1 : -1] ) }

这显然只适用于C ++。 This article讨论了修改它以便在C中使用的方法。

答案 5 :(得分:3)

正如Leander所说,静态断言正被添加到C ++ 11中,现在它们已经存在。

static_assert(exp, message)

例如

#include "myfile.hpp"

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

void doStuff(MyClass object) { }

请参阅上面的cppreference page

答案 6 :(得分:2)

编译最终二进制文件时,将MY_COMPILER_ASSERT定义为空白,以使其输出不包含在结果中。只能按照调试方式进行定义。

但实际上,你不可能以这种方式捕捉每一个断言。有些在编译时没有意义(就像断言值不为null)。您所能做的就是验证其他#defines的值。我不确定你为什么要那样做。

答案 7 :(得分:1)

使用'#error'是一个有效的预处理器定义,导致编译在大多数编译器上停止。你可以这样做,例如,为了防止在debug中编译:


#ifdef DEBUG
#error Please don't compile now
#endif

答案 8 :(得分:0)

我发现这可以为GCC提供最简单的错误消息。其他所有东西都有一些关于负面大小或其他令人困惑的东西的后缀:

#define STATIC_ASSERT(expr, msg)   \
typedef char ______Assertion_Failed_____##msg[1];  __unused \
typedef char ______Assertion_Failed_____##msg[(expr)?1:2] __unused

示例用法:

 unsigned char testvar;
 STATIC_ASSERT(sizeof(testvar) >= 8, testvar_is_too_small);

gcc中的错误消息(ARM / GNU C编译器:6.3.1):

conflicting types for '______Assertion_Failed_____testvar_is_too_small'

答案 9 :(得分:-1)

好吧,你可以使用static asserts in the boost library

我相信他们在那里做的是定义一个数组。

 #define MY_COMPILER_ASSERT(EXPRESSION) char x[(EXPRESSION)];

如果EXPRESSION为true,则定义char x[1];,这是正常的。如果为false,则定义char x[0];是非法的。

相关问题