什么是“断言”功能?


261

我一直在研究OpenCV教程,并遇到了该assert功能。它有什么作用?


20
请注意man assert的注释:“ assert()被实现为宏;如果测试的表达式具有副作用,则程序行为将取决于是否定义了NDEBUG而有所不同。这可能会产生Heisenbug,而在打开调试功能后,Heisenbug就会消失”。
Johannes Schaub-litb


35
@ S.Lott现在搜索Google的人们将找到该页面作为搜索结果之一,这是一个很好的同行评论问题的答案,并同时促进Stack Overflow,因此我的+1!
马特·格鲁姆

6
这是我的-1。首要搜索结果应该是OP首先应该查阅的文档
Lightness Races in Orbit

9
@LightnessRacesinOrbit。称我为懒惰者,但我更喜欢使用像您这样有知识的SO用户提供的出色的精炼,总结和解释性阅读材料。我由衷的感谢:)
泰森·希尔默

Answers:


297

assert如果其参数结果为false,则将终止程序(通常在消息中引用assert语句)。如果发生意外情况,通常在调试过程中使程序更明显地失败。

例如:

assert(length >= 0);  // die if length is negative.

如果失败,您还可以添加一条更多信息的消息来显示,如下所示:

assert(length >= 0 && "Whoops, length can't possibly be negative! (didn't we just check 10 lines ago?) Tell jsmith");

否则像这样:

assert(("Length can't possibly be negative! Tell jsmith", length >= 0));

在进行发布(非调试)构建时,通常还可以使用编译器开关assert来定义NDEBUG宏,从而消除评估语句的开销。必然的结果是您的程序永远不应依赖于assert宏的运行。

// BAD
assert(x++);

// GOOD
assert(x);    
x++;

// Watch out! Depends on the function:
assert(foo());

// Here's a safer way:
int ret = foo();
assert(ret);

从程序调用abort()的组合,并不能保证做任何事情,断言应仅用于测试开发人员已经假设的事情,而不是例如,用户输入数字而不是字母(应该是通过其他方式处理)。


49
assert通常会引发异常”-在C ++中,它不会引发“异常”,它称为中止...这有点不同。
Artyom

5
我认为此答案与问题被标记的语言(C和C ++)的语言不同。在C和C ++中,assert不会引发异常,它的参数周围带有括号,并且#字符不会引入注释。
史蒂夫·杰索普

您可以选择以下选项:使答案成为社区Wiki,并编辑您的问题以删除旧内容,要求其他人提供正确的信息。这样一来,否决票就不会影响您的代表,人们可以改善答案。
Johannes Schaub-litb

2
如果length为NaN,则length> = 0也会变成false
Andreas

1
当前,在VS 2015中,带有错误消息的断言将不起作用,因为它的顺序不正确。应该是assert("error message", expression)
Dooskington '16

102

断言计算机语句是类似于声明补充一定的英语。


131
好吧,不完全是。“确保指示灯熄灭”的意思是“检查指示灯,如果指示灯熄灭,则将其关闭”,而不是“查看指示灯是否熄灭,否则请爆炸”。我会说“仔细检查”是更接近的翻译。
Luke Maurer

7
@Luke展示了英语的奇妙之处,因为它提供了多种表达同一事物的方式。但是考虑assert(length> 0)。因此,我们可以将其转换为“仔细检查长度是否大于零”“确保长度大于零”。虽然这两种选择显然都可行,但我还是更喜欢我的;)
Blake7

4
是的,但是另一种解释是“ 使长度大于零”。因此,还有更多的歧义。
Luke Maurer

29
它更紧密地类似于英语动词“断言”
史蒂夫·卡特

实际上,“确保”可以解释为“检查,如果没有,则进行更改”。
Erik Bongers

14

看一眼

assert()C ++中的示例程序

许多编译器提供assert()宏。assert()宏如果其参数评估为TRUE,则返回TRUE;如果评估为FALSE,则采取某种措施。许多编译器会在assert()失败的情况下中止程序;其他人会抛出异常

assert()宏的一项强大功能是,如果未定义DEBUG,则预处理器会将其折叠为任何代码。这在开发过程中提供了很大的帮助,并且在最终产品出厂时,不会降低性能,也不会增加程序的可执行版本的大小。

例如

#include <stdio.h>
#include <assert.h>

void analyze (char *, int);

int main(void)
{
   char *string = "ABC";
   int length = 3;

   analyze(string, length);
   printf("The string %s is not null or empty, "
          "and has length %d \n", string, length);
}

void analyze(char *string, int length)
{
   assert(string != NULL);     /* cannot be NULL */
   assert(*string != '\0');    /* cannot be empty */
   assert(length > 0);         /* must be positive */
}

/****************  Output should be similar to  ******************
The string ABC is not null or empty, and has length 3

11
不应该是“如果定义了NDEBUG”吗?
RichN

2
那“许多编译器”是什么意思?MSVC和GCC都符合这一标准。哪些不兼容的编译器:没有断言;当断言失败时,不要打印消息并中止;使用DEBUG而不是正确的NDEBUG?
史蒂夫·杰索普

lmao,当然这是一个名为java-samples的网站,它使这一点如此错误。
underscore_d

6

assert()函数可以诊断程序错误。在C中,它是在中定义的<assert.h>;在C ++中,是在中定义的<cassert>。它的原型是

void assert(int expression);

参数表达式可以是您要测试的任何内容-变量或任何C表达式。如果expression的计算结果为TRUE,则assert()不执行任何操作。如果expression的计算结果为FALSE,则assert()在stderr上显示错误消息,并中止程序执行。

您如何使用assert()?它最常用于跟踪程序错误(与编译错误不同)。错误不会阻止程序编译,但是会导致程序给出错误的结果或运行不正确(例如,锁定)。例如,您正在编写的财务分析程序有时可能会给出错误的答案。您怀疑问题是由变量interest_rate取负值引起的,这种情况永远不会发生。要对此进行检查,请放置以下语句

断言(interest_rate> = 0); 在程序中使用interest_rate的位置。如果变量确实变为负数,则assert()宏会警告您。然后,您可以检查相关代码以找到问题的原因。

要查看assert()的工作方式,请运行下面的示例程序。如果输入非零值,程序将显示该值并正常终止。如果输入零,则assert()宏将强制程序异常终止。您看到的确切错误消息将取决于您的编译器,但这是一个典型示例:

断言失败:x,文件list19_3.c,第13行请注意,为了使assert()工作,必须在调试模式下编译程序。有关启用调试模式的信息,请参阅您的编译器文档(稍后说明)。当您以后在发布模式下编译最终版本时,assert()宏将被禁用。

 int x;

 printf("\nEnter an integer value: ");
 scanf("%d", &x);

 assert(x >= 0);

 printf("You entered %d.\n", x);
 return(0);

输入一个整数值:10

您输入了10。

输入一个整数值:-1

错误消息:异常程序终止

您的错误消息可能会有所不同,具体取决于您的系统和编译器,但是总体思路是相同的。


您解释了示例程序如何与assert一起工作。断言本身如何工作。如何异常终止程序?它会引发某种异常吗?哪一种?它会产生特殊信号吗?什么信号 断言可以被任何一种C ++ try-catch机制“捕获”吗?
Motti Shneor '16

4

对于大多数编译器而言,诸如“引发异常”和“停止执行”之类的东西可能适用,但并非对所有编译器都适用。(顺便说一句,有没有断言确实引发异常?)

这是c6x和其他TI编译器使用的assert的一个有趣的,略有不同的含义:在看到某些assert语句后,这些编译器使用该语句中的信息来执行某些优化。邪恶。

在C中的示例:

int dot_product(short *x, short *y, short z)
{
  int sum = 0
  int i;

  assert( ( (int)(x) & 0x3 ) == 0 );
  assert( ( (int)(y) & 0x3 ) == 0 );

  for( i = 0 ; i < z ; ++i )
    sum += x[ i ] * y[ i ];
  return sum;
}

这告诉de编译器数组在32位边界上对齐,因此编译器可以生成针对这种对齐方式的特定指令。


1
那么,如果断言为假且未设置NDEBUG时,他们该怎么办?如果这是来自<assert.h>的assert的版本,则该标准要求它打印一条消息并中止。显然,该标准并没有说不允许它们基于语句的真实性进行优化,但是要使其合规,如果错误则仍然必须中止。
史蒂夫·杰索普

1
他们遵循标准行为
stijn

1

C ++ 11 N3337标准草案

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

19.3断言

1表42中描述的标头<cassert>提供了用于记录C ++程序断言的宏以及用于禁用断言检查的机制。

2内容与标准C库头文件<assert.h>相同。

C99 N1256标准草案

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

7.2诊断<assert.h>

1标头<assert.h>定义断言宏,并引用另一个NDEBUG未由定义的宏<assert.h>。如果NDEBUG在源文件中包含<assert.h>的位置将其定义为宏名称,则将assert宏定义为

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

每次<assert.h>包含时,将根据NDEBUG的当前状态重新定义断言宏 。

2.断言宏应实现为宏,而不是实际功能。如果取消了宏定义以访问实际功能,则该行为未定义。

7.2.1程序诊断

7.2.1.1断言宏

概要

1。

#include <assert.h>
void assert(scalar expression);

描述

2 assert宏将诊断测试放入程序中;它扩展为一个空表达式。执行时,如果expression(应为标量类型)为false(即比较等于0),则assert宏将写入有关失败的特定调用的信息(包括自变量的文本,源文件,源行号和封装函数的名称-后者分别是标准错误流上实现定义格式的预处理宏__FILE____LINE__标识符 的值__func__。165)然后调用中止功能。

退货

3 assert宏不返回任何值。


1

在else和printf上正常使用assert()函数的三个主要原因

  1. assert()函数主要用于调试阶段,如果您每次要测试条件(即使在最终代码中都无法实现)时都使用printf语句编写代码,则很麻烦。

  2. 在大型软件部署中,assert非常方便,您可以在为assert()函数链接头文件之前,使用定义的NDEBUG宏使编译器忽略assert语句。

  3. 当您在设计函数或某些代码时,想要获得关于代码将受到什么限制并且不起作用的想法时,assert()会派上用场。


0

如果该函数评估的值为false,它将停止程序执行。通常,它被一个宏包围,因此使用发行版设置编译时,它不会编译成结果二进制文件。

它旨在用于测试您所做的假设。例如:

void strcpy(char* dest, char* src){
    //pointers shouldn't be null
    assert(dest!=null);
    assert(src!=null);

    //copy string
    while(*dest++ = *src++);
}

您想要的理想选择是您可以在程序中出错,例如调用带有无效参数的函数,并且在断言错误(或无法按预期工作)之前命中一个断言。


为什么我们不只使用if&else&添加一些日志记录信息呢?
阿萨德·汗

1
因为您永远不应该将空指针传递给strcpy。这是不应该发生的事情之一。如果传递了空指针,则表明更高级别的内容已混乱。充其量您最终将不得不由于意外的数据值(dest不正确或为null)而使程序进一步崩溃,从而使问题不再崩溃。
Yacoby,2009年

-5

另外,您可以使用它来检查动态分配是否成功。

代码示例:

int ** p;
p = new int * [5];      // Dynamic array (size 5) of pointers to int
for (int i = 0; i < 5; ++i) {
    p[i] = new int[3]; // Each i(ptr) is now pointing to a dynamic
                       // array (size 3) of actual int values
}

assert (p);            // Check the dynamic allocation.

相似:

if (p == NULL) {
    cout << "dynamic allocation failed" << endl;
    exit(1);
}

3
否。new除非您指定nothrow(在此处未指定),否则分配失败将引发异常。此外,您的格式化很奇怪而且exit很邪恶。
Lightness Races in Orbit

是的,你是对的。我忘了补充一下。很想看看您将如何使用它而不是退出
Sadikov '16

1
@Sadikov任何其他人都将使用在发布模式下构建时完全删除的代码来处理这种错误情况,从而使您的用户无法受到保护,并且如果不满足前提条件,将受到未定义行为的支配,这是由于从来没有检查和抓住assert()仅用于调试和淘汰不应该,永远,永远不会发生的事情-在构建发行版之前很久。
underscore_d
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.