为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C
长风秋雁 2019-12-27 15:28:08
我想实现一个“声明”来防止编译,而不是在错误情况下在运行时失败。我目前有一个这样的定义,效果很好,但是会增加二进制文件的大小。#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;}示例代码(无法编译)。#define DEFINE_A 1#define DEFINE_B 1MY_COMPILER_ASSERT(DEFINE_A == DEFINE_B);我该如何实现它,使其不生成任何代码(以最小化生成的二进制文件的大小)?
查看完整描述

3 回答

?
犯罪嫌疑人X

TA贡献2080条经验 获得超4个赞

可以在纯标准C语言中进行编译时声明,并且一点点预处理程序的欺骗使其使用看上去与的运行时用法一样干净assert()。


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


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


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


我通常的解决方案是要求宏具有两个参数。第一个是断言为真的条件,第二个是在后台声明的类型名称的一部分。答案是通过令牌粘贴和__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:错误:数组`assertion_failed_demo_c_32'的大小为负

$


查看完整回答
反对 回复 2019-12-27
?
胡子哥哥

TA贡献1825条经验 获得超6个赞

下面的COMPILER_VERIFY(exp)宏效果很好。


//合并参数(在扩展参数之后)

#定义GLUE(a,b)__GLUE(a,b)

#定义__GLUE(a,b)a ## b


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


#定义COMPILER_VERIFY(exp)验证(exp,__LINE__)

它适用于C和C ++,并且可以在允许typedef的任何地方使用。如果表达式为true,则为1个字符的数组生成typedef(无害)。如果表达式为假,它将为-1个字符的数组生成一个typedef,这通常会导致错误消息。作为折衷形式的表达式可以是任何可求出编译时常数的表达式(因此,涉及sizeof()的表达式可以正常工作)。这使它比


#if(expr)

#错误

#万一

在这里,您只能使用可以由预处理程序求值的表达式。


查看完整回答
反对 回复 2019-12-27
?
慕村9548890

TA贡献1884条经验 获得超4个赞

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


查看完整回答
反对 回复 2019-12-27
  • 3 回答
  • 0 关注
  • 742 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信