3

假设我有一个功能可以执行一个很小的特定任务,该任务很有可能失败。处理出错的最佳方法是什么?(假设我知道问题是什么)。

例如,假设我有一个读取两个字节字符串并返回它的函数:

#include <stdio.h>
#include <stdlib.h>

char *bar(void)
{
    char *foo = malloc(3);
    scanf("%2s", foo);
    return foo;
}

int main(void)
{
    char *foo = bar();
    puts(foo);
    free(foo);
    return 0;
}

上面的示例绝对没有任何错误处理。有两种方法可以实现某种错误处理,但我不确定哪种方法更受欢迎或被认为是最佳实践。

方法 1(从函数内部将错误消息打印到 stderr):

#include <stdio.h>
#include <stdlib.h>

char *bar(void)
{
    char *foo;
    if(!(foo = malloc(3)))
    {
        fputs("\nError! Memory allocation failed.", stderr);
        return 0x00;
    }
    scanf("%2s", foo);
    return foo;
}

int main(void)
{
    char *foo;
    if(!(foo = bar())) return 1;
    puts(foo);
    free(foo);
    return 0;
}

方法2(从调用函数打印错误信息到stderr):

#include <stdio.h>
#include <stdlib.h>

char *bar(void)
{
    char *foo;
    if(!(foo = malloc(3))) return 0x00;
    scanf("%2s", foo);
    return foo;
}

int main(void)
{
    char *foo;
    if(!(foo = bar()))
    {
        fputs("\nError! Memory allocation failed.", stderr); 
        return 1;
    }
    puts(foo);
    free(foo);
    return 0;
}

我几乎认为方法二将是最好的方法,因为这样我可以更具体地了解我的错误消息,具体取决于我当时调用该函数的目的。对于方法二,我担心的是,如果函数有多个潜在故障点,我将无法打印函数中具体出了什么问题。

伪代码:

 IF FAILUREA
     PRINT "FAILUREA OCCURED"
     RETURN
 IF FAILUREB
     PRINT  "FAILUREB OCCURED"
     RETURN

如果我调用的函数是 an,这不会有太大问题,int因为那时我可以根据错误返回一个不同的整数值。但是在 a 的情况下,char*我通常会尝试NULL在失败时返回(所以两者FAILUREA都会FAILUREB返回NULL);没有办法知道导致该功能失败的原因。

所以我的问题是处理错误消息时的最佳实践是什么?

4

3 回答 3

2

允许调用者处理错误报告会更好,因为:

  • 如果该功能构成库的一部分,stderr则可能不可用,并且需要替代报告机制。
  • 调用代码可能具有可以采取的替代操作,并且可能不会将功能失败bar()视为实际失败并且不需要报告它。

如果一个函数有多个可能的失败原因,那么可以将一个参数传递给在失败时更新的函数。然后调用函数可以根据实际失败原因选择适当的操作。例如:

enum Status
{
    STATUS_OK,
    STATUS_MEMORY_ALLOCATION_FAILURE,
    STATUS_ACCESS_DENIED
};

enum Status status;
char* foo = bar(&status);
if (!foo)
{
    if (STATUS_MEMORY_ALLOCATION_FAILURE == status)
    {
        /* report failure. */
    }
    else if (STATUS_ACCESS_DENIED == status)
    {
        /* try somewhere else */
    }
}
于 2012-10-10T13:41:29.743 回答
2

如果你能对失败做任何事情,如果你打算这样做,那么你就去做。否则,你可以实现一个通用的失败函数,在出错的情况下调用它,然后每天调用它:

void error(const char* format, ...)
{
  va_list vl;
  va_start(vl, format);
  vfprintf(stderr, format, vl);
  va_end(vl);
  exit(-1);
}

您可以选择将其包装在一个宏中,并为其提供行号和文件名:

#define ERROR(fmt, ...) \
  error("file:'%s',line:%d " fmt, __FILE__, __LINE__, __VA_ARGS__)

这将使控制台中的错误非常容易找出,因为错误消息准确地告诉了文件和其中发生错误的行。

典型用法,没什么花哨的:

char *bar(void)
{
  char *foo;
  if ((foo=malloc(3)) == NULL)
    ERROR("malloc() failed!\n");
  if (scanf("%2s", foo) != 1)
    ERROR("scanf() failed!\n");
  return foo;
}

如果您想在错误发生时实际执行某些操作,您可以使用longjmp()代替exit(-1)立即返回调用者(=执行相应操作的调用者),可能会关闭所有打开的文件以进行写入,这样缓冲的数据就不会丢失。setjmp()

例如,如果您正在编写一个简单的编译器,对于编译error()器内部的大多数错误以及正在编译的源代码中的问题(例如缺少冒号/括号或其他导致代码不可编译)。

如果您不能或不想这样做,则需要仔细编写代码,进行适当的清理并返回不同的错误代码以将可操作的错误传达给调用者。

于 2012-10-10T13:56:50.267 回答
1

如果您的函数返回超过 1 个错误情况,您可以这样做

#include <stdio.h>
#include <stdlib.h>

int bar(char **foo)
{
    if(!(malloc(3))) return 1; /* return error case 1*/
    scanf("%2s", *foo);
    if(!(malloc(4))) return 2; /* return error case 2*/
    return 0; /* no error*/
}

int catcherror(int error)
{
    switch (error) {
         case 1: 
             /*do something 1*/
         case 2: 
             /*do something 1*/
         case 3: 
             /*do something 1*/
         case 4: 
             /*do something 1*/
         case 5: 
             /*do something 1*/
         default: 
             /*do something 1*/
     }
}

int main(void)
{
    char *foo;
    int error

    error = bar(&foo);
    catcherror(error);
    puts(foo);
    free(foo);
    return 0;
}

如果您的项目包含许多返回常见错误情况的函数,该catcherror()函数可能非常有用

于 2012-10-10T13:51:53.953 回答