我的讲师在课堂上问过我,我想知道为什么它是一个宏而不是一个函数?
答案 0 :(得分:70)
简单的解释是标准要求assert
成为一个宏,如果我们查看draft C99 standard(,据我所知,这些部分在{{{ 3}}以及)部分7.2
诊断段 2 说:
断言宏应实现为宏,而不是实际宏 功能。如果禁止宏定义以访问 实际功能,行为未定义。
为什么需要这个,draft C11 standard中给出的理由是:
使断言成为真正的函数可能很困难或不可能,因此它仅限于宏 形式。
这不是很有用,但我们可以从其他要求中看出原因。回到7.2
段 1 部分说:
[...]如果NDEBUG被定义为源文件中的宏名称 在包含的地方,断言宏被简单地定义为
#define assert(ignore) ((void)0)
根据NDEBUG的当前状态重新定义断言宏 每次都包括在内。
这很重要,因为它允许我们在发布模式下关闭断言的简单方法,您可能需要花费可能昂贵的支票。
并且第二个重要要求是需要使用宏__FILE__
,__LINE__
和__func__
,这将在7.2.1.1
部分中介绍宏说:
[...]断言宏写入有关特定呼叫的信息 失败的后者分别是后者的价值观 预处理宏__FILE_ _和__LINE_ _以及标识符 __func_ _)在标准错误流中以实现定义的格式。 165)然后调用中止函数。
脚注165
说:
写入的信息可能是以下形式:
Assertion failed: expression, function abc, file xyz, line nnn.
将它作为宏允许在正确的位置评估宏__FILE__
等......并且Joachim指出是宏允许它插入原始的表达式它产生的信息。
C ++标准草案要求cassert
标题的内容与Standrd C库的assert.h
标题相同:
内容与标准C库标题相同。
参见:ISO C 7.2。
为什么(无效)0?
为什么使用(void)0
而不是其他什么表达式?我们可以提出几个原因,首先是断言概要在7.2.1.1
部分中的含义:
void assert(scalar expression);
它说(强调我的):
断言宏将诊断测试放入程序中; 扩展为无效表达。
表达式(void)0
与最终得到 void表达式的需要一致。
假设我们没有这个要求,其他可能的表达式可能会产生不良影响,例如允许在发布模式下使用assert
,这在调试模式下是不允许的,例如使用Rationale for International Standard—Programming Languages—C将允许我们在分配中使用assert
,如果使用正确,可能会生成expression result unused
警告。至于使用复合语句作为评论建议,我们可以从plain 0
看到它们在某些情况下会产生不良影响。
答案 1 :(得分:45)
__FILE__
)和行号(通过__LINE__
)assert
替换在发布模式下构建时无效(即((void)0)
)的有效表达式答案 2 :(得分:13)
如果在包含时已经定义了名为NDEBUG的宏,则禁用此宏。这允许编码器在调试程序时在源代码中包含所需数量的断言调用,然后通过简单地包括如下行来禁用生产版本的所有调用:
#define NDEBUG
在其代码的开头,包含<assert.h>
之前。
因此,此宏旨在捕获编程错误,而不是用户或运行时错误,因为它通常在程序退出调试阶段后被禁用。
将其作为函数将增加一些函数调用,并且您无法在释放模式下控制所有此类断言。
如果您使用函数,则_FILE__
,__LINE__
和__func__
将给出该断言函数的代码值。不是呼叫线路或呼叫功能线路。
答案 3 :(得分:13)
一些断言调用起来可能很昂贵。您刚刚编写了一个高性能矩阵反转例程,并添加了一个完整性检查
assert(is_identity(matrix * inverse))
到最后。好吧,你的矩阵非常大,如果assert
是一个函数,那么在将它传递给assert之前需要花费大量的时间来进行计算。如果您没有进行调试,那么您真的不想花时间。
或许断言相对便宜,但它包含在一个非常短的函数中,将在内循环中调用。或其他类似的情况。
通过改为使用assert
宏,可以在关闭断言时完全取消计算。
答案 4 :(得分:1)
为什么断言宏而不是函数?
因为它应该以 DEBUG 模式编译,不应该以 RELEASE 模式编译。