当前位置: 首页 > 知识库问答 >
问题:

为什么assert是宏而不是函数?

淳于思淼
2023-03-14

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

共有3个答案

石臻
2023-03-14

如果在包含时已经定义了名为NDEBUG的宏,则禁用该宏。这允许编码器在调试程序时在源代码中包含所需的任意多个断言调用,然后通过简单地包含以下行来禁用生产版本中的所有断言调用:

#define NDEBUG 

在其代码的开头,在包含

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

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

如果使用函数,则_FILE____LINE__<-code>和 将给出该断言函数代码的值。不是调用线或调用函数的线。

史经业
2023-03-14
  1. 它允许捕获文件(通过__file__)和行号(通过__line__<-code>)
  2. 它允许断言替换为在发布模式下构建时不执行任何操作的有效表达式(即((void)0)>)

姜业
2023-03-14

简单的解释是,该标准要求< code>assert是一个宏,如果我们看一下C99标准草案(据我所知,C11标准草案中的部分也是一样的)第< code>7.2节诊断第2段说:

断言宏应该作为一个宏来实现,而不是作为一个实际的函数来实现。如果为了访问实际函数而抑制了宏定义,则行为是未定义的。

为什么需要这样做,国际标准编程语言的基本原理-C中给出的基本原理是:

要使assert成为一个真正的函数可能很困难或不可能,因此它仅限于宏形式。

这不是非常有用的,但是我们可以从其他需求中看出为什么。回到第7.2节,第1段说:

[...]如果在源文件中包含的位置将NDEBUG定义为一个宏名,那么assert宏就简单地定义

#define assert(ignore) ((void)0)

断言宏在每次包含时都会根据 NDEBUG 的当前状态重新定义。

这一点很重要,因为它允许我们在发布模式下关闭断言,您可能希望承担潜在昂贵检查的成本。

第二个重要要求是,需要使用宏< code>__FILE__ 、< code>__LINE__和< code>__func__,这在第< code>7.2.1.1节断言宏中有所介绍,它说:

[…]断言宏以实现定义的格式写入关于失败的特定调用的信息[…]后者分别是标准错误流上预处理宏__FILE__和__LINE__和标识符__func__)的值。165)然后调用中止函数。

其中脚注< code>165说:

所写的消息可能采用以下形式:

Assertion failed: expression, function abc, file xyz, line nnn.

将其作为宏允许宏__FILE__etc…在适当的位置进行评估,正如Joachim指出的那样,作为宏允许它在生成的消息中插入原始表达式。

C标准草案要求< code>cassert头的内容与标准C库中的< code>assert.h头的内容相同:

内容与标准C库标题相同。

另请参见:ISO C 7.2。

< b >为什么(void)0?

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

void assert(scalar expression);

上面写着(重点是我的):

assert宏将诊断测试放入程序中;它扩展为一个空表达式。

表达式< code>(void)0符合以void表达式结尾的需要。

假设我们没有这个要求,其他可能的表达式可能会产生不良影响,例如允许在发布模式下使用<code>assert</code>,这在调试模式下是不允许的,例如,使用纯<code>0</code>将允许我们在赋值中使用<code<assert</code>,并且如果使用正确,可能会生成<code>表达式结果未使用</code>警告。至于像注释所建议的那样使用复合语句,我们可以从C多行宏:do/while(0)vs作用域块中看到,在某些情况下,它们可能会产生不良影响。

 类似资料:
  • 在Julia中,打印格式化字符串的语法如下: 为什么是宏而不是函数?这样它就可以接受不同数量的参数了吗?

  • 当我只运行预处理器时,输出文件包含20。 然而,据我所知,预处理器只是进行文本替换。所以这就是我认为正在发生的事情(这显然是错误的,但idky): NUM被定义为10 所以我认为输出应该是10而不是20。有什么能解释出哪里出了问题吗?

  • 我试图理解背后的动机。如果已经存在类型类和,为什么有必要呢? 诚然,的实例是具体的类型,而的实例需要一个类型参数。(有关有用的解释,请参见Monoid vs MonadPlus。)但是你不能重写任何类型的约束吗 作为和的组合? 从。它的实施是: 我只能使用和实现它: 有人能澄清和之间的真正区别吗?

  • 问题内容: 这一直使我感到困惑。看起来这样会更好: 比这个: 是否有特定原因? 问题答案: 这是因为任何可迭代对象都可以连接(例如,列表,元组,字典,集合),但是其内容和“连接器” 必须是 字符串。 例如: 使用字符串以外的其他东西会引发以下错误:

  • rank ▲ ✰ vote url 37 511 93 416 url 为什么是string.join(list)而不是list.join(string)? 我一直被这个问题困扰.如果这样写更好: my_list = ["Hello", "world"] print my_list.join("-") # Produce: "Hello-world" 而不是: my_list = ["Hello

  • 问题内容: 我已经在Android代码中使用FloatBuffers一段时间了(从一些opengles教程中复制了它),但是我无法确切地理解此构造是什么以及为什么需要它。 例如,我在许多人的代码和android教程中看到了以下代码(或类似代码): 就我所知,这似乎是冗长和混乱的,我只是说它们只是一个浮点数的包装而已。 问题: 与任何其他类型的float集合或简单数组相反,这种类型的类(ByteBu