我应该检查函数的每个参数以确保函数正常运行吗?

eli*_*inx 6 c api parameter-passing

我注意到标准 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);
}
Run Code Online (Sandbox Code Playgroud)

还有许多其他人没有进行相同的验证。这是一个好的做法吗?

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

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;

    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

Art*_*Art 4

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

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

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

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

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

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