第二个问题是关于使用#if指令来实现断言,这个问题比较严重:标准C和C++不识别在#if条件中的sizeof 和offsetof。它们也不能识别在#if条件中的枚举常数。一些编译器允许在#if中作为扩展存在sizeof、offsetof和枚举常数,但是大多数编译器是不允许的。庆幸的是,你可以以另外一种没有这种限制的方式来写编译时断言。
编译时断言的无效声明
在C和C++中,规定一个数组声明中元素数量的一个常数表达式必须具有一个正值。例如: int w[10]; int x[1];
是有效的数组声明,而int y[0]; 不是。一个常数维数组具有多个操作数和运算符,包括sizeof 和offsetof子表达式,例如: int z[2 * sizeof(w) / sizeof(w[0])];
这声明数组z具有两倍于数组w的元素。
开发者可以利用常数数组的维数必须是正数的要求,来以宏的形式实现编译时断言: #define compile_time_assert(cond) \ char assertion[(cond) ? 1 : 0]
如果x是一个评估为真的表达式,于是调用: compile_time_assert(x);
扩展到一个有效的数组声明(一维)。否则,扩展到一个无效的数组声明(0维),这个数组声明产生一个编译时诊断消息(错误或者告警)。
然而,当断言失败时错误消息的文本对于不同的编译器是不同的。笔者看到过的消息如“数组必须至少具有一个元素”,或者“无效的脚本或者脚本过大”。
如果幸运,编译器产生包含数组名的一个消息,例如“数组大小‘断言’为零”。在那种情况下,它帮助使数组名成为一个额外的宏参数,如下: #define compile_time_assert(cond, msg) \ char msg[(cond) ? 1 : 0]
然后,你可以使用数组名来描述断言失败的原因。例如,如果调用: compile_time_assert(offsetof(timer, DATA) == 4,} DATA_must_be_at_offset_4);
造成一个断言失败,那么将可能看到一个错误消息,类似于: size of array 'DATA_must_be_at_offset_4' is zero
如上所述,这个宏有一个很容易解决的小问题。这个问题是,在某些情况下,数组声明可能是分配存储的一个定义。可以将数组声明转变为typedef来避免这个问题,如: #define compile_time_assert(cond, msg) \ typedef char msg[(cond) ? 1 : 0]
在相同的范围内,两个typedefs不能具有相同的名字,因此必须使用msg参数来给每个typedef一个独特的名字。如果不愿意采用msg参数,那么可以将数组声明为外部数组,如下所示: #define compile_time_assert(cond) \ extern char assertion[(cond) ? 1 : 0]
然而,如果采用这种方法,将不能在C++类中使用宏,因为你不能将C++类成员声明为外部量。你可以发现编译器不会对0大小的数组提出“抱怨”。在这种情况下,你可能尝试将0改变为-1,如下所示: #define compile_time_assert(cond, msg) \ typedef char msg[(cond) ? 1 : -1]
Boost库(www.boost.org)为C++程序员提供了另一种以称为BOOST_STATIC_ASSERT的宏的形式实现编译时断言的方法。利用C++模板可以巧妙地实现这个宏。如果你是一个C++程序员,而且你理解模板的特殊性,你可以进行尝试。
作者:Dan Saks 总裁 Email:dsaks@wittenberg.edu Saks & Associates公司
[此贴子已经被作者于2005-11-28 15:36:16编辑过] |