最理想的处理功能错误消息的方法?

时间:2012-10-10 13:22:03

标签: c function error-handling return-value stderr

假设我有一个函数来执行一个很小的特殊任务,它有很好的失败可能性。处理出错的最佳方法是什么? (假设我知道问题是什么)。

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

#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

如果我调用的函数是int,这不是什么大问题,因为那样我就可以根据出错的地方返回一个不同的整数值。但是在char*的情况下,我通常会在失败时尝试返回NULL(因此FAILUREAFAILUREB都将返回NULL);没有办法知道导致功能失败的原因。

所以我的问题是在处理错误消息时最佳做法是什么?

3 个答案:

答案 0 :(得分: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 */
    }
}

答案 1 :(得分: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()对于编译器内部的大多数错误以及编译源代码中的问题(例如缺少冒号/ paren)来说已经足够了或其他使代码无法编译的东西。)

如果你不能或不想做任何这些,你需要仔细编写代码,进行适当的清理并返回不同的错误代码,以便向调用者传达可操作的错误。

答案 2 :(得分: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()函数可能非常有用