有没有更好的方法来处理C风格的错误?

有没有更好的方法来处理C风格的错误?,c,error-handling,C,Error Handling,我试图通过编写一个简单的解析器/编译器来学习C。到目前为止,这是一次非常有启发性的经历,但是由于我有很强的C#背景,我在调整方面遇到了一些问题——特别是在缺乏例外的情况下 现在我已经读过了,我同意那篇文章中的每一个字;在我的C#代码中,我尽可能避免抛出异常,但是现在我面临一个无法抛出异常的世界,我的错误处理完全淹没了代码中原本干净易读的逻辑 目前,我正在编写代码,如果出现问题,代码需要快速失败,而且可能嵌套得很深——我已经确定了一种错误处理模式,即“Get”函数在错误时返回NULL,其他函数在失

我试图通过编写一个简单的解析器/编译器来学习C。到目前为止,这是一次非常有启发性的经历,但是由于我有很强的C#背景,我在调整方面遇到了一些问题——特别是在缺乏例外的情况下

现在我已经读过了,我同意那篇文章中的每一个字;在我的C#代码中,我尽可能避免抛出异常,但是现在我面临一个无法抛出异常的世界,我的错误处理完全淹没了代码中原本干净易读的逻辑

目前,我正在编写代码,如果出现问题,代码需要快速失败,而且可能嵌套得很深——我已经确定了一种错误处理模式,即“Get”函数在错误时返回NULL,其他函数在失败时返回
-1
。在这两种情况下,失败的函数都会调用
NS\u SetError()
,因此调用函数所需要做的就是清理并在出现故障时立即返回

我的问题是,
if(Action()<0)的数量返回-1语句让我头疼——它非常重复,完全模糊了底层逻辑。我最终为自己创建了一个简单的宏来尝试改善这种情况,例如:

#define NOT_ERROR(X)    if ((X) < 0) return -1

int NS_Expression(void)
{
    NOT_ERROR(NS_Term());
    NOT_ERROR(Emit("MOVE D0, D1\n"));

    if (strcmp(current->str, "+") == 0)
    {
        NOT_ERROR(NS_Add());
    }
    else if (strcmp(current->str, "-") == 0)
    {
        NOT_ERROR(NS_Subtract());
    }
    else
    {
        NS_SetError("Expected: operator");
        return -1;
    }
    return 0;
}
/* Exception macro */
#define TRY_EXIT(Cmd)   { if (!(Cmd)) {goto EXIT;} }

/* My memory allocator */
char * MyAlloc(int bytes)
{
    char * pMem = NULL;

    /* Must have a size */
    TRY_EXIT( bytes > 0 );

    /* Allocation must succeed */
    pMem = (char *)malloc(bytes);
    TRY_EXIT( pMem != NULL );

    /* Initialize memory */
    TRY_EXIT( initializeMem(pMem, bytes) != -1 );

    /* Success */
    return (pMem);

EXIT:

    /* Exception: Cleanup and fail */
    if (pMem != NULL)
        free(pMem);

    return (NULL);
}
#如果((X)<0)返回-1,则定义非错误(X)
int NS_表达式(void)
{
NOT_错误(NS_Term());
NOT_错误(发射(“移动D0,D1\n”);
如果(strcmp(当前->str,“+”==0)
{
NOT_错误(NS_Add());
}
否则如果(strcmp(当前->str,“-”==0)
{
NOT_错误(NS_减法());
}
其他的
{
NS_设置错误(“预期:操作员”);
返回-1;
}
返回0;
}
每个函数
NS_Term
NS_Add
NS_Subtract
都执行
NS_SetError()
并在出现错误时返回
-1
,这样更好,但它仍然感觉我在滥用宏,不允许进行任何清理(有些函数,特别是返回指针的
Get
函数更复杂,需要运行清理代码)

总的来说,我觉得我遗漏了一些东西——尽管以这种方式处理错误应该更容易识别,但在我的许多函数中,我确实很难确定是否正确处理了错误:

  • 某些函数在出现错误时返回
    NULL
  • 某些函数在出现错误时返回
    <0
  • 有些函数从不产生错误
  • 我的函数执行一个
    NS\u SetError()
    ,但许多其他函数不执行
有没有更好的方法来组织我的功能,或者其他人也有这个问题


另外,让
Get
函数(返回指向对象的指针)在错误上返回
NULL
是一个好主意,还是它只是混淆了我的错误处理?

一种清理技术是使用一个while循环,该循环永远不会真正迭代。它提供了goto而不使用goto

#define NOT_ERROR(x) if ((x) < 0) break;
#define NOT_NULL(x) if ((x) == NULL) break;

// Initialise things that may need to be cleaned up here.
char* somePtr = NULL;

do
{
    NOT_NULL(somePtr = malloc(1024));
    NOT_ERROR(something(somePtr));
    NOT_ERROR(somethingElse(somePtr));
    // etc

    // if you get here everything's ok.
    return somePtr;
}
while (0);

// Something went wrong so clean-up.
free(somePtr);
return NULL;
#如果((x)<0)中断,则定义非错误(x);
#如果((x)==NULL)中断,则定义NOT_NULL(x);
//在此处初始化可能需要清理的内容。
char*somePtr=NULL;
做
{
NOT_NULL(somePtr=malloc(1024));
非错误(something(somePtr));
非错误(somethingElse(sometptr));
//等
//如果你到了这里,一切都好。
返回sometr;
}
而(0);
//出了点问题,所以清理一下。
免费(somePtr);
返回NULL;
但你会失去一定程度的缩进


编辑:我想补充一点,我并不反对goto,只是对于提问者的用例来说,他并不真的需要它。有些情况下,使用goto可以击败任何其他方法,但这不是其中之一。

你可能不喜欢听这个,但C方式的例外是。这是原因之一它是用语言写的

另一个原因是
goto
是状态机实现的自然表达式。状态机最能代表什么常见编程任务?词法分析器。请查看
lex
someone.Gotos的输出


因此,在我看来,现在是你熟悉语言语法元素的时候了,
goto

在每次
从错误中返回之前都要重复相同的最终代码,这是一个更大的问题。在这种情况下,人们普遍接受使用
goto

int func ()
{
  if (a() < 0) {
    goto failure_a;
  }

  if (b() < 0) {
    goto failure_b;
  }

  if (c() < 0) {
    goto failure_c;
  }

  return SUCCESS;

  failure_c:
  undo_b();

  failure_b:
  undo_a();

  failure_a:
  return FAILURE;
}
<> >代码< > <代码>是一个短路运算符,上面将替换三个单独的<代码> < < /代码>。考虑使用链接在<代码>返回< /代码>语句中:

return (a() < 0 || b() < 0 || c() < 0) ? FAILURE : SUCCESS;
返回(a()<0 | | b()<0 | | c()<0)?失败:成功;

必须至少从两个层面考虑这一点:函数如何交互,以及函数中断时如何操作

我看到的大多数大型C框架总是通过引用返回状态和“返回”值(WinAPI和许多C Mac OS API就是这种情况)。你想返回布尔值吗

StatusCode FooBar(int a, int b, int c, bool* output);
要返回指针吗

StatusCode FooBar(int a, int b, int c, char** output);
好吧,你明白了

在调用函数方面,我最常看到的模式是使用指向清理标签的goto语句:

    if (statusCode < 0) goto error;

    /* snip */
    return everythingWentWell;

error:
    cleanupResources();
    return somethingWentWrong;
if(statusCode<0)转到错误;
/*剪断*/
把一切都归还给我们;
错误:
清洁资源();
返回一些不愉快的事情;
这个怎么样

int NS_Expression(void)
{
    int ok = 1;
    ok = ok && NS_Term();
    ok = ok && Emit("MOVE D0, D1\n");
    ok = ok && NS_AddSub();
    return ok
}

goto语句是实现异常样式处理的最简单且可能最干净的方法。如果在宏参数中包含比较逻辑,则使用宏将更易于阅读。如果您组织例程以执行正常(即无错误)操作工作并仅在异常情况下使用goto,它对于读取来说相当干净。例如:

#define NOT_ERROR(X)    if ((X) < 0) return -1

int NS_Expression(void)
{
    NOT_ERROR(NS_Term());
    NOT_ERROR(Emit("MOVE D0, D1\n"));

    if (strcmp(current->str, "+") == 0)
    {
        NOT_ERROR(NS_Add());
    }
    else if (strcmp(current->str, "-") == 0)
    {
        NOT_ERROR(NS_Subtract());
    }
    else
    {
        NS_SetError("Expected: operator");
        return -1;
    }
    return 0;
}
/* Exception macro */
#define TRY_EXIT(Cmd)   { if (!(Cmd)) {goto EXIT;} }

/* My memory allocator */
char * MyAlloc(int bytes)
{
    char * pMem = NULL;

    /* Must have a size */
    TRY_EXIT( bytes > 0 );

    /* Allocation must succeed */
    pMem = (char *)malloc(bytes);
    TRY_EXIT( pMem != NULL );

    /* Initialize memory */
    TRY_EXIT( initializeMem(pMem, bytes) != -1 );

    /* Success */
    return (pMem);

EXIT:

    /* Exception: Cleanup and fail */
    if (pMem != NULL)
        free(pMem);

    return (NULL);
}

除了
goto
,标准C还有另一个结构来处理异常的流控制
setjmp/longjmp
/* Exception macro */
#define TRY_EXIT(Cmd)   { if (!(Cmd)) {goto EXIT;} }

/* My memory allocator */
char * MyAlloc(int bytes)
{
    char * pMem = NULL;

    /* Must have a size */
    TRY_EXIT( bytes > 0 );

    /* Allocation must succeed */
    pMem = (char *)malloc(bytes);
    TRY_EXIT( pMem != NULL );

    /* Initialize memory */
    TRY_EXIT( initializeMem(pMem, bytes) != -1 );

    /* Success */
    return (pMem);

EXIT:

    /* Exception: Cleanup and fail */
    if (pMem != NULL)
        free(pMem);

    return (NULL);
}
if (bla) NOT_ERROR(X);
else printf("wow!\n");
#define NOT_ERROR(X)          \
  if ((X) >= 0) { (void)0; }  \
  else return -1
int my_atoi(const char *str, int *val)
{
        // convert str to int
        // store the result in *val
        // return 0 on success, -1 (or any other value except 0) otherwise
}
int Foo(void)
{
    // Initialise things that may need to be cleaned up here.
    char* somePtr = malloc(1024);
    if (somePtr = NULL)
    {
        return NULL;
    }

    if (FooInner(somePtr) < 0)
    {
        // Something went wrong so clean-up.
        free(somePtr);
        return NULL;
    }

    return somePtr;
}

int FooInner(char* somePtr)
{
    if (something(somePtr) < 0) return -1;
    if (somethingElse(somePtr) < 0) return -1;
    // etc

    // if you get here everything's ok.
    return 0;
}
#include <setjmp.h>
#include <stdio.h>

jmp_buf x;

void f()
{
    longjmp(x,5); // throw 5;
}

int main()
{
    // output of this program is 5.

    int i = 0;

    if ( (i = setjmp(x)) == 0 )// try{
    {
        f();
    } // } --> end of try{
    else // catch(i){
    {
        switch( i )
        {
        case  1:
        case  2:
        default: fprintf( stdout, "error code = %d\n", i); break;
        }
    } // } --> end of catch(i){
    return 0;
}
#include <stdio.h>
#include <setjmp.h>

#define TRY do{ jmp_buf ex_buf__; if( !setjmp(ex_buf__) ){
#define CATCH } else {
#define ETRY } }while(0)
#define THROW longjmp(ex_buf__, 1)

int
main(int argc, char** argv)
{
   TRY
   {
      printf("In Try Statement\n");
      THROW;
      printf("I do not appear\n");
   }
   CATCH
   {
      printf("Got Exception!\n");
   }
   ETRY;

   return 0;
}