【问题标题】:Should I check every single parameter of a function to make sure the function works well?我应该检查函数的每个参数以确保该函数运行良好吗?
【发布时间】:2016-09-06 07:18:23
【问题描述】:

我注意到标准 c 库包含几个不检查输入参数(是否为 NULL)的字符串函数,例如 strcmp:

int strcmp(const char *s1, const char *s2)
{
    for ( ; *s1 == *s2; s1++, s2++)
    if (*s1 == '\0')
        return 0;
    return ((*(unsigned char *)s1 < *(unsigned char *)s2) ? -1 : +1);
}

还有许多其他人不进行相同的验证。这是一个好习惯吗?

在其他库中,我看到他们检查每个参数,如下所示:

int create_something(int interval, int mode, func_t cb, void *arg, int id)
{
    if (interval == 0) return err_code_1;
    if (valid(mode))   return err_code_2;
    if (cb == NULL)    return err_code_3;
    if (arg == NULL)   return err_code_4;
    if (id == 0)       return err_code_5;

    // ...
}

哪个更好?当您设计一个 API 时,您会检查所有参数以使其正常运行还是任其崩溃?

【问题讨论】:

  • 最好提供强类型,因此不需要检查。然后检查更好,但它不是免费的。因此出于性能原因,在几个接口中都省略了检查(但应该在文档中)。
  • This talk 详细介绍了如何执行此操作以及原因。简而言之,您必须了解后果(绩效、合同变更)、做出决定、记录并保持一致。
  • 只要您记录约束并说未定义的行为,否则出于性能原因,它可能是一个合理的选择。
  • 这又是真正应该标记为 C 或 C++,但不能同时标记的问题之一。 C 根本没有 private 函数,而在 C++ 中,有一种情况是 private 函数不需要检查它的参数(因为它们是从 public 函数调用的)
  • @MSalters C 没有 private 关键字,但 C 当然可以通过 static 函数来拥有私有函数。此外,关于 C 与 C++,我认为这是一个与语言无关的哲学问题。

标签: c api parameter-passing


【解决方案1】:

我想说的是,在期望有效指针的库函数中不检查 NULL 的指针实际上比返回错误或默默地忽略它们更好。

NULL 不是唯一的无效指针。有数十亿个其他指针值实际上是不正确的,为什么我们要优先处理一个值?

错误返回经常被忽略、误解或管理不善。忘记检查一个错误返回可能会导致程序行为不端。我想争辩说,一个默默地行为不端的程序比一个根本不起作用的程序更糟糕。不正确的结果可能比没有结果更糟糕。

早期和困难的失败可以简化调试。这是最大的原因。程序的最终用户不希望程序崩溃,但作为程序员,我是库的最终用户,我实际上希望它崩溃。崩溃表明我需要修复一个错误,我们越快遇到错误并且崩溃越接近错误的来源,我就能越快越容易找到并修复它。 NULL 指针取消引用是最容易捕获、调试和修复的错误之一。这比在数千兆字节的日志中找到一行“create_something has a null pointer”要容易得多。

使用错误返回,如果调用者捕获到该错误,自己返回一个错误(在您的示例中为err_create_something_failed)并且其调用者返回另一个错误(err_caller_of_create_something_failed)怎么办?然后你有一个错误返回 3 个函数,这甚至可能无法表明实际出了什么问题。即使它设法指出实际出了什么问题(通过一个完整的错误处理框架来准确记录错误在整个调用者链中发生的位置),你唯一能做的就是在一些表并由此得出结论,create_something 中有一个NULL 指针。相反,如果您可以打开一个调试器并确切地查看违反假设的位置以及导致该问题的确切函数调用链,那就太痛苦了。

本着同样的精神,您可以使用assert 来验证其他函数参数,从而导致早期且易于调试的故障。断言崩溃,您拥有导致问题的完整正确调用链。我只是不会使用断言来检查指针,因为它毫无意义(至少在具有内存管理的操作系统上)并且在给您相同的行为(减去打印的消息)的同时使事情变慢。

【讨论】:

    【解决方案2】:

    您可以使用 assert.h 来检查您的参数:

    assert(pointer != NULL);
    

    如果“ponter == NULL”,这将使程序在调试模式下失败,但在发布时根本不会检查,因此您可以检查您想要的所有内容,而不会影响性能。

    无论如何,如果一个函数需要在某个范围内检查参数,这会浪费资源,那么应该由 API 的用户进行检查。

    但如何设计 API 取决于您。在这个问题上没有正确的方法:如果一个函数需要一个介于 1 和 5 之间的数字并且用户传递一个 6,您可以执行检查或简单地指定该函数将具有未定义的行为。

    【讨论】:

      【解决方案3】:

      没有普遍正确的方法来执行参数验证。一般来说,您应该尽可能使用assert 来验证参数,但assert 通常在非调试版本中被禁用,并且可能并不总是合适的。

      需要考虑的事项可能因情况而异,例如:

      • 您希望您的函数被称为 lot 吗?性能很关键吗?如果调用者将在紧密循环中多次调用您的函数,那么验证参数可能会很昂贵。这对于内联函数尤其不利,并且如果验证检查的运行时成本比函数其余部分的运行时成本相形见绌。
      • 调用者执行检查是否容易?如果检查不重要,那么在函数本身中进行验证比强制调用者进行额外工作更不容易出错。请注意,在某些情况下,调用者甚至可能无法自己执行正确的验证(例如,如果在检查参数的有效性时可能出现竞争条件)。
      • 您的函数是否有据可查?它是否清楚地描述了它的先决条件,指定了它的参数的有效值是什么?如果是这样,那么您通常应该认为调用者有责任传递有效的参数。
      • 您的函数是自记录的吗?调用者是否清楚有效的参数是什么?
      • 应该传递一个错误的参数是 logic 错误还是 runtime 错误?也就是说,它应该被认为是程序员的错误吗?该论点是否可能直接来自用户输入?你应该考虑你期望调用者如何使用你的函数。如果启用了断言,错误的参数是否应该致命并终止程序?
      • 您的函数的用户是谁?您的函数将在内部使用(您可能对使用它的其他程序员的能力有一些期望),还是将向公众公开?如果是后者,哪种故障模式可以最大限度地减少您需要提供的技术支持量? (我对dropt library 采取的立场是,我依靠断言来验证内部函数的参数并报告公共函数的错误代码。)

      【讨论】:

        【解决方案4】:

        我注意到标准 c 库包含几个不检查输入参数(是否为 NULL)的字符串函数,例如 strcmp:

        标准 C 库的字符串句柄函数要求“......此类调用中的指针参数仍应具有有效值......”C11dr §7.24.1 2

        NULL 不是指向字符串的有效指针,并且函数部分不需要检查指针的有效性,因此不需要 NULL 检查。
        C 的性能确实是有代价的。

        当您设计一个 API 时,您会检查所有参数以使其正常运行还是任其崩溃?

        这进入了设计理念。考虑一个更简单的例子。 API 是否应该首先测试输入参数?这取决于编码目标。

        int add(int a, int b) {
          return a + b;
        }
        
        // return 1 on failure
        int add_safe(int *sum, int a, int b) {
          if (a >= 0) { 
            if (b > INT_MAX - a) return 1; // Overflow
          } else { 
            if (b < INT_MIN - a) return 1; // Underflow
          } if (sum) { // NULL check
            *sum = a + b;
          }
          return 0;
        }
        

        如有疑问,请创建一个执行名义检查的 API。对可能来自用户(人)或其他进程的输入进行强检查。如果您想进行繁重的迂腐检查,C 不是有效的目标语言选择。

        在我制作的许多 API 中,NULL 是作为指针的有效输入值,代码会根据上述内容调整其功能。进行了NULL 检查,但它不是错误 检查。

        【讨论】:

        • 你的例子很有见地,谢谢。最后,你说NULL 是有效输入,NULL check 不是错误检查,这是什么意思,对不起,但我没明白。
        • @elinx 对于strcmp()NULL 不是有效输入,就像float x; strcmp(&amp;x, ...) 不是有效输入一样。代码不应期望 NULL&amp;x 被验证为 strcmp() 调用的一部分。对于我创建的许多函数,NULL 是一个有效的输入。因此需要对NULL 进行检查。这不是因为NULL 是一个错误,而是因为NULL 表示不同的功能。
        猜你喜欢
        • 1970-01-01
        • 2010-09-16
        • 1970-01-01
        • 2020-07-04
        • 1970-01-01
        • 2015-08-07
        • 2015-01-31
        • 2013-02-17
        • 1970-01-01
        相关资源
        最近更新 更多