为什么断言宏而不是函数?

Ita*_*Bar 58 c assert c-preprocessor

我的讲师在课堂上问过我,我想知道为什么它是一个宏而不是一个函数?

Sha*_*our 70

简单的解释是,标准要求assert是一个宏,如果我们看一下C99标准草案(据我可以告诉部分是在同C11标准草案以及)部分7.2 诊断2说:

断言宏应实现为宏,而不是实际功能.如果为了访问实际函数而禁止宏定义,则行为是未定义的.

为什么需要这个,国际标准程序设计语言-C的基本原理给出的理由是:

断言一个真正的函数可能很困难或不可能,因此它仅限于宏形式.

这不是很有用,但我们可以从其他要求中看出原因.回到7.21节,说:

[...]如果NDEBUG被定义为包含源文件中的点的宏名称,则断言宏被简单地定义为

#define assert(ignore) ((void)0)
Run Code Online (Sandbox Code Playgroud)

断言宏根据每次包含的NDEBUG的当前状态重新定义.

这很重要,因为它允许我们在发布模式下关闭断言的简单方法,您可能需要花费可能昂贵的检查费用.

第二个重要的要求是它需要使用宏__FILE__,__LINE__ 并且__func__7.2.1.1 断言宏中描述了这一点:

[...] assert宏写入有关特定调用的信息,后者分别是标准错误流上的预处理宏__FILE_ _和__LINE_ _以及标识符__func_ _的值.实现定义的格式.165)然后调用中止函数.

脚注165说:

写入的消息可能是以下形式:

Assertion failed: expression, function abc, file xyz, line nnn.
Run Code Online (Sandbox Code Playgroud)

将它作为一个宏允许宏__FILE__等等......在适当的位置进行评估,并且Joachim指出它是一个宏,允许它在它生成的消息中插入原始表达式.

C++标准草案要求cassert标题的内容assert.h与Standrd C库中的标题相同:

内容与标准C库头相同.

另见:ISO C 7.2.

为什么(无效)0?

为什么使用(void)0而不是其他表达式什么都不做?我们可以提出几个原因,首先这是断言概要的部分内容7.2.1.1:

void assert(scalar expression);
Run Code Online (Sandbox Code Playgroud)

它说(强调我的):

断言宏将诊断测试放入程序中; 它扩展为void表达式.

表达式(void)0与最终得到void表达式的需要一致.

假设我们没有这个要求,其他可能的表达式可能会产生不良影响,例如assert允许在调试模式下使用释放模式,例如使用plain0将允许我们assert在赋值中使用,并且当正确使用时可能会生成一个expression result unused警告.至于使用复合语句作为注释建议,我们可以从C多行宏看到:do/while(0)vs scope block它们在某些情况下会产生不良影响.

  • @Drew McGowen:在C99中,它只有一个类型为`_Bool`的参数作为"特定类型".任何标量类型都可以隐式转换为`_Bool`. (2认同)

Rob*_*ahy 44

  1. 它允许捕获文件(通过__FILE__)和行号(通过__LINE__)
  2. 它允许assert替换有效的表达式,该表达式((void)0)在以释放模式构建时不起作用(即)

  • 确切地说,通过`((void)0)`. (13认同)
  • `assert`必须始终是有效的*表达式*."空字符串"不是有效的表达式,这就是为什么`assert`不能被空字符串替换的原因.我可以像这样使用`assert`:`a =(assert(b> 0),b)`.这是完全有效的表达.如果`assert`突然变成空字符串,则该表达式将变为无效. (7认同)
  • 此外,它还允许实际表达式在错误消息中表示为字符串. (5认同)
  • 你们都误解了.什么Joachim肉是'断言(1 == 2)`可以打印像"断言失败:1 == 2"的消息.除非它是一个宏,否则这是不可能的. (5认同)
  • @RobertAllanHenniganLeahy:他在说什么.这个断言(根据标准)不是由空字符串替换,而是在定义NDEBUG时使用字符串`((void)0)`. (2认同)

Jee*_*tel 13

如果在包含时已经定义了名为NDEBUG的宏,则禁用此宏.这允许编码器在调试程序时根据需要在源代码中包含尽可能多的断言调用,然后通过简单地包括如下行来禁用生产版本的所有这些调用:

#define NDEBUG 
Run Code Online (Sandbox Code Playgroud)

在其代码的开头,包含之前<assert.h>.

因此,此宏旨在捕获编程错误,而不是用户或运行时错误,因为它通常在程序退出调试阶段后被禁用.


将其作为函数将增加一些函数调用,并且您无法在释放模式下控制所有此类断言.

如果您使用的功能,那么_FILE__,__LINE____func__将给予该assert函数的代码的值.不是调用行或调用函数的行.


小智 13

一些断言调用起来可能很昂贵.您刚刚编写了一个高性能矩阵反转例程,并添加了一个完整性检查

assert(is_identity(matrix * inverse))
Run Code Online (Sandbox Code Playgroud)

到最后.好吧,你的矩阵非常大,如果assert是一个函数,在将它传递给assert之前需要花费大量的时间来进行计算.如果您没有进行调试,那么您真的不想花时间.

或许断言相对便宜,但它包含在一个非常短的函数中,将在内循环中调用.或其他类似的情况.

通过制作assert宏,您可以在关闭断言时完全取消计算.