纯标准 C 中的编译时断言是可能的,并且一点点预处理器技巧使它的使用看起来像在运行时使用assert()
.
关键技巧是找到一个可以在编译时评估并且可能导致某些值错误的构造。一个答案是数组的声明不能有负大小。使用 typedef 可以防止成功时分配空间,并保留失败时的错误。
错误消息本身将隐晦地引用负大小的声明(GCC 说“数组 foo 的大小为负”),因此您应该为数组类型选择一个名称,暗示此错误确实是一个断言检查。
另一个需要处理的问题是,在任何编译单元中只能为typedef
特定类型名称指定一次。因此,宏必须为每个用法安排一个唯一的类型名称来声明。
我通常的解决方案是要求宏有两个参数。第一个是断言为真的条件,第二个是在幕后声明的类型名称的一部分。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 演示.c
demo.c:32:错误:数组“assertion_failed_demo_c_32”的大小为负
$