【问题标题】:When should we use asserts in C?我们什么时候应该在 C 中使用断言?
【发布时间】:2011-11-13 19:21:57
【问题描述】:

我正在用 C 编写一个函数。就风格而言,与返回错误代码相比,何时使用断言更好。假设该函数正在划分两个数字。我应该断言除数不为零还是应该返回错误代码? 如果可以的话,请举出更多的例子,以明确区分。

【问题讨论】:

  • asserts 只能在调试模式下工作,不是吗?所以它们仅用于测试。我将其作为答案发布,因为我知道 C++ 就是这种情况,但我不确定 C 的情况。
  • 上面cmets中的两个链接是指C++异常的问题。在 C 中不是一个选项。

标签: c


【解决方案1】:

assert 中止进程,但在使用-DNDEBUG 编译程序时变成空操作,因此它是一个相当粗糙的调试工具,仅此而已。您应该只使用assert 来检查“不可能发生”的情况,例如这违反了算法的不变量或后置条件,但可能用于输入验证(当然不在库中)。当检测到来自客户端的无效输入时,请友好并返回错误代码。

assert 的一个示例用法可能是:您已经实现了一个非常智能的排序算法,并且您想检查它是否真的排序了。由于排序函数应该“正常工作”,因此不会返回值,因此您无法在不更改 API 的情况下添加错误返回。

void sort(int *a, size_t n)
{
    recursive_super_duper_sort(a, 0, n);
    assert(is_sorted(a, n));
}

static bool is_sorted(int const *a, size_t n)
{
    for (size_t i=0; i<n-1; i++)
        if (a[i] > a[i+1])
            return false;

    return true;
}

从长远来看,您确实需要一个合适的单元测试框架而不是 assert,但它作为临时调试工具很有用。

【讨论】:

    【解决方案2】:

    错误代码表示运行时行为。断言是一种调试工具,允许开发人员断言他们关于程序逻辑的假设确实是正确的。

    它们是两个完全不同的东西,具有不同的应用程序。

    错误代码是您正常程序流程的一部分。断言用于调试,如果触发了断言,则意味着您的程序没有正确编写。

    【讨论】:

      【解决方案3】:

      通常,断言是为了让程序员(即您)在将程序发布给真实用户之前发现逻辑/编程错误。断言不应该用于检测运行时输入错误——对这些使用错误代码。

      【讨论】:

        【解决方案4】:

        这真的是一个品味问题。这是我的看法。

        主要的经验法则:断言失败总是程序中的错误。

        如果您希望调用者确保参数正确并且您想指出任何其他行为是调用者中的错误,请使用assert 检查函数参数。除以零是,IMO,一个很好的例子。

        如果您希望调用者在调用之前无法确保参数正确,请使用错误代码。例如,事先检查参数的计算成本可能非常高。

        切勿使用assert 来检查用户输入。

        【讨论】:

          【解决方案5】:

          传统的智慧是使用 assert() 来帮助调试代码,当“不可能”、不能发生的事情发生时向您发出警告。这个“警告”采取退出程序的形式。

          我听说 Jim Coplien(通用 C++ 大师和 SCRUM 培训师)主张让您的断言在已部署的代码中保持活动状态。 (这听起来很疯狂,我知道......)这是专门针对高可靠性服务器代码的。这样做的动机是,与其容忍你的服务器处于“不可能”状态,不如失败、艰难并让另一个节点接管。

          (当然,跟踪失败并分析它们。这意味着存在错误或不正确的假设。)

          【讨论】:

            【解决方案6】:

            首先,来自&lt;assert.h&gt; 标头的assert 可以被禁用(例如,通过使用gcc -DNDEBUG 编译),有时对于二进制的“生产”版本会被禁用。

            其次,如 Linux 手册页所述,

               The  purpose  of  this macro is to help the programmer find bugs in his
               program.   The  message  "assertion  failed  in  file  foo.c,  function
               do_bar(), line 1287" is of no help at all to a user.
            

            所以断言应该只在错误的情况下失败。在异常或错误情况下,您应该做其他事情。

            一些工具(甚至编译器)可能会使用assert-ions 来例如优化你的代码。

            在您的 quotient 函数示例中,如果在整个程序中,您确定除数应该是非零的,那么您将使用 assert(但是以不同的方式命名函数可能是有意义的) ,也许是quotient_by_non_zero)。如果您认为它可能会发生,请将其设为致命消息、异常(即 C 中的 longjmp)、错误代码等。

            【讨论】:

              【解决方案7】:

              由于 C 不支持异常,除了返回错误代码之外,您别无选择。一个失败的 C assert() 导致 abort() 被调用,这会破坏进程。这与标准错误处理无法真正相比。

              对于浮点运算,您可以使用NaN 来指示错误情况。对于整数运算,错误代码是您唯一的选择。

              【讨论】:

              • 有些人(包括我在内)认为longjmp 可用于在 C 中实现穷人的例外。
              • @BasileStarynkevitch 没错,但我们谈论的是极端贫困! ;-)
              【解决方案8】:

              当您的程序遇到不允许继续的情况时使用断言。断言是“合同”,我将它们用作与操作系统的“合同”以及“延迟危险”的情况。

              为了模拟异常,您仍然可以使用 GOTO 'ERRORLABEL' 并在运行清理功能后终止清理。

              【讨论】:

                【解决方案9】:

                这是我昨天写的一个断言的真实例子。

                我有两个并行数组——我们称它们为ab——我正要运行一个从0 到a 大小的索引i,然后用@ 做一些事情987654325@ 和b[i]。但是如果b 中的元素少于a 中的元素,我的代码将在b 上出现数组边界冲突。但是代码的其他部分应该保持ab 的大小相同。所以我在循环之前放了一个断言,断言ab 的大小是相等的。

                大小应该相等——但如果它们不相等,我希望代码在告诉我原因的断言上失败,而不是在可能导致的未定义行为上奇怪地失败当我尝试读取数组 b 之外的内容时,如果它更小的话。

                【讨论】:

                  【解决方案10】:

                  assert 的本质是捕捉程序员认为不应该发生但可能发生的错误

                  让我们看看它的实际效果以及它为什么对我们有帮助。

                  我们设计了一个带有控制语句的函数来测试我们的动物是狗还是猫。我们的代码只处理狗和猫,作为程序员,我们相信 else 语句永远不会执行。但由于未知错误(意外初始化和分配、未检测到的溢出等),它可能执行。

                  void isDogOrCat(T_animal x){
                     if(isDog(x)){
                         printf("Woof\n");
                     }
                     else if(isCat(x)){
                         printf("Meow\n");
                     }
                     else{
                         assert(false);
                     }
                  }
                  

                  如果发生错误,我们会立即指向失败的断言,因此我们的调试尝试轻而易举。将其与仅拥有:

                     if(isDog(x)){
                         printf("Woof\n");
                     }
                     else{
                         printf("Meow\n");
                     }
                  

                  如果这里有漏洞,你的麻烦就会大得多。您会认为由于输出而传入了一只猫,但您不知道的是,您正在处理一个错误地超出您设计范围的变量!

                  (可以添加 else if(isCat(x)) 而不是 else,但在这种情况下,两个语句都不会执行,函数也不会输出任何内容——再次违反我们的设计)

                  【讨论】:

                    猜你喜欢
                    • 2018-03-25
                    • 2021-09-07
                    • 1970-01-01
                    • 2010-12-30
                    • 2014-11-09
                    • 1970-01-01
                    • 2011-07-04
                    • 2017-09-13
                    相关资源
                    最近更新 更多