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