C编译器断言 – 如何实现?
我想实现一个“断言”,防止编译,而不是在运行时,在错误情况下失败。
我现在有一个这样的定义,这很好,但增加了二进制文件的大小。
#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);
我怎样才能实现这个,以便它不生成任何代码(为了最小化生成的二进制文件的大小)?
纯C标准的编译时间是可能的,一些预处理器的诡计使得它的使用看起来和assert()
的运行时使用一样干净。
关键的技巧是find一个可以在编译时进行评估的构造,并且可能会导致某些值出错。 一个答案是数组的声明不能有负大小。 使用typedef可以防止成功分配空间,并在失败时保留错误。
错误信息本身会隐含地指向负大小的声明(GCC表示“大小的数组foo是负数”),所以你应该select一个数组types的名称,暗示这个错误确实是一个断言检查。
另一个要处理的问题是,只能在任何编译单元中键入一次特定的types名称。 所以,macros必须安排每个用法来获取唯一的types名称来声明。
我通常的解决办法是要求macros有两个参数。 第一个是断言的条件是真的,第二个是在幕后声明的types名称的一部分。 基础的答案暗示了使用标记粘贴和__LINE__
预定义macros来形成一个唯一的名称,可能不需要额外的参数。
不幸的是,如果断言检查包含在一个包含文件中,它仍然可能与第二个包含文件中的相同行号码或主源文件中该行号码的检查冲突。 我们可以通过使用macros__FILE__
,但是它被定义为一个string常量,并且没有可以将string常量返回到标识符名称部分的预处理技巧; 更不用说合法的文件名可以包含不是标识符合法部分的字符。
所以,我会build议下面的代码片段:
/** 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 -c demo.c demo.c:32:错误:数组“assertion_failed_demo_c_32”的大小为负值 $
以下COMPILER_VERIFY(exp)
macros工作得很好。
//组合参数(扩展参数之后) #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的地方使用。 如果expression式为真,它将为1个char(这是无害的)数组生成一个typedef。 如果expression式为false,则会为-1个字符的数组生成一个typedef,这通常会导致出现错误消息。 给出的expression式可以是任何能够计算编译时常量的expression式(所以涉及到sizeof()的expression式工作正常)。 这比它更灵活
#if(expr) #错误 #万一
您只能使用预处理器可以评估的expression式。
我可以在C中find静态断言的最佳写法是在pixelbeat 。 请注意,静态断言正在被添加到C ++ 0X中,并且可能会使它进入C1X,但是这不会有一段时间。 我不知道我给的链接中的macros是否会增加你的二进制文件的大小。 我怀疑他们不会,至less如果你在一个合理的优化水平编译,但你的里程可能会有所不同。
如果你的编译器设置了像DEBUG或NDEBUG这样的预处理macros,你可以这样做(否则你可以在Makefile中设置):
#ifdef DEBUG #define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;} #else #define MY_COMPILER_ASSERT(EXPRESSION) #endif
然后,您的编译器仅为debugging构build断言。
我知道你对C感兴趣,但是看看boost的C ++ static_assert 。 (顺便说一下,这可能在C ++ 1x中可用。)
对于C ++,我们又做了类似的事情:
#define COMPILER_ASSERT(expr)enum {ARG_JOIN(CompilerAssertAtLine,__LINE__)= sizeof(char [(expr)?+1:-1])}
这显然只适用于C ++。 本文讨论一种方法来修改它在C中使用。
编译最后的二进制文件时,将MY_COMPILER_ASSERT定义为空白,以便输出不包含在结果中。 只能按照debugging方式进行定义。
但是,真的,你不可能通过这种方式来捕捉每一个断言。 有些只是在编译时没有意义(如断言值不为空)。 你所能做的就是validation其他#defines的值。 我不确定你为什么要这样做。
使用'#error'是一个有效的预处理器定义,导致编译在大多数编译器上停止。 你可以这样做,例如,为了防止在debugging中编译:
#ifdef DEBUG #error Please don't compile now #endif
正如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页面 。
那么,你可以使用boost库中的static asserts
。
我相信他们在那里做的是定义一个数组。
#define MY_COMPILER_ASSERT(EXPRESSION) char x[(EXPRESSION)];
如果EXPRESSION为真,则定义char x[1];
,这是确定的。 如果为false,则定义char x[0];
这是非法的。