简单的解释是标准要求assert 是一个宏,如果我们查看draft C99 standard(据我所知draft C11 standard 中的部分也是相同的) 部分7.2 诊断 段落2 说:
assert 宏应作为宏实现,而不是实际的
功能。如果宏定义被抑制以访问
实际功能,行为未定义。
为什么需要这个,Rationale for International Standard—Programming Languages—C 给出的理由是:
使 assert 成为真正的函数可能很困难或不可能,因此仅限于宏
表格。
这不是很丰富,但我们可以从其他要求中看出原因。回到7.2 段 1 部分说:
[...]如果 NDEBUG 在源文件中被定义为宏名
包含在哪里,断言宏被简单地定义为
#define assert(ignore) ((void)0)
根据NDEBUG的当前状态重新定义assert宏
每次都包含在内。
这很重要,因为它使我们能够以一种简单的方式在发布模式下关闭断言,在这种情况下您可能想要承担可能昂贵的检查成本。
第二个重要要求是需要使用宏__FILE__、__LINE__ 和__func__,这在7.2.1.1 部分中有介绍。断言宏 说:
[...] assert 宏写入有关特定调用的信息
失败的 [...] 后者分别是
预处理宏 __FILE_ _ 和 __LINE_ _ 和标识符
__func__) 以实现定义的格式在标准错误流上。165) 然后它调用中止函数。
脚注165 说:
所写的消息可能是以下形式:
Assertion failed: expression, function abc, file xyz, line nnn.
将其作为宏允许宏 __FILE__ 等在适当的位置进行评估,并且正如 Joachim 指出的宏允许它在消息中插入原始 表达式它会生成。
C++ 标准草案要求 cassert 标头的内容与标准 C 库中的 assert.h 标头的内容相同:
内容与标准C库头文件相同。
另请参阅:ISO C 7.2。
为什么 (void)0?
为什么使用(void)0 而不是其他什么都不做的表达式?我们可以想出几个原因,首先这是7.2.1.1部分中断言概要的样子:
void assert(scalar expression);
它说(强调我的):
assert 宏将诊断测试放入程序中;它扩展为一个空表达式。
表达式 (void)0 与需要以 void 表达式结尾的需求一致。
假设我们没有这个要求,其他可能的表达式可能会产生不良影响,例如允许在发布模式下使用 assert,而在调试模式下不允许使用,例如使用 plain 0 将允许我们使用 @987654345 @ 在分配中并且正确使用时可能会生成 expression result unused 警告。至于评论建议使用复合语句,我们可以从C multi-line macro: do/while(0) vs scope block 看到它们在某些情况下会产生不良影响。