在C中的每次错误检查后如何避免长链的免费(或删除)?

假设我非常防御地编写代码,并且总是检查我调用的所有函数的返回类型。

所以我喜欢:

char* function() { char* mem = get_memory(100); // first allocation if (!mem) return NULL; struct binder* b = get_binder('regular binder'); // second allocation if (!b) { free(mem); return NULL; } struct file* f = mk_file(); // third allocation if (!f) { free(mem); free_binder(b); return NULL; } // ... } 

注意free()事物失控的速度有多快。 如果某些function失败,我必须先释放每一个分配。 代码很快变得丑陋,我所做的就是将所有内容复制粘贴。 我成为了一个复制/粘贴程序员,更糟糕的是,如果有人在其间添加一个声明,他必须修改下面的所有代码来调用free()来添加它。

经验丰富的C程序员如何解决这个问题? 我无法解决任何问题。

谢谢,Boda Cydo。

您可以定义一个可以释放资源的新标签 ,然后您可以在代码失败时进行GOTO。

 char* function() { char* retval = NULL; char* mem = get_memory(100); // first allocation if (!mem) goto out_error; struct binder* b = get_binder('regular binder'); // second allocation if (!b) goto out_error_mem; struct file* f = mk_file(); // third allocation if (!f) goto out_error_b; /* ... Normal code path ... */ retval = good_value; out_error_b: free_binder(b); out_error_mem: free(mem); out_error: return retval; } 

这里已经讨论了使用GOTO 进行错误管理:在C中有效使用goto进行错误管理?

我知道我会因此而被私刑,但我有一个朋友说他们使用了goto

然后他告诉我,在大多数情况下这还不够,他现在使用了setjmp() / longjmp() 。 基本上他重新发明了C ++的例外但却不那么优雅。

也就是说,由于goto 可以工作,你可以将它重构为不使用goto东西,但缩进会快速失控:

 char* function() { char* result = NULL; char* mem = get_memory(100); if(mem) { struct binder* b = get_binder('regular binder'); if(b) { struct file* f = mk_file(); if (f) { // ... } free(b); } free(mem); } return result; } 

顺便说一下,在块周围散布局部变量声明就像那不是标准C.

现在,如果你意识到free(NULL); 由C标准定义为无操作,可以简化嵌套一些:

 char* function() { char* result = NULL; char* mem = get_memory(100); struct binder* b = get_binder('regular binder'); struct file* f = mk_file(); if (mem && b && f) { // ... } free(f); free(b); free(mem); return result; } 

虽然我很佩服你的防御编码方法,这是一件好事。 每个C程序员都应该有这种心态,它也适用于其他语言……

我不得不说这是关于GOTO的一件有用的事情,尽管纯粹主义者会另有说法,这将是一个相当于一个终极块,但有一个特别的问题,我可以看到那里…

karlphillip的代码几乎完成但是……假设函数是这样完成的

  char* function() { struct file* f = mk_file(); // third allocation if (!f) goto release_resources; // DO WHATEVER YOU HAVE TO DO.... return some_ptr; release_resources: free(mem); free_binder(b); return NULL; } 

小心!!! 这取决于你认为合适的函数的设计和目的,放在一边..如果你从这样的函数返回,你可能最终通过release_resources标签下降……这可能会导致微妙的bug,所有对堆上指针的引用都消失了,最终可能会返回垃圾…所以请确保你已经分配了内存并将其返回,在标签之前使用return关键字,否则内存可能会消失…或创建内存泄漏….

您也可以采取相反的方法并检查是否成功:

 struct binder* b = get_binder('regular binder'); // second allocation if(b) { struct ... *c = ... if(c) { ... } free(b); } 

如果您的数据结构是复杂/嵌套的,那么单个goto可能就不够了,在这种情况下我建议如下:

 mystruct = malloc(sizeof *mystruct); if (!mystruct) goto fail1; mystruct->data = malloc(100); if (!mystruct->data) goto fail2; foo = malloc(sizeof *foo); if (!foo) goto fail2; ... return mystruct; fail2: free(mystruct->data); fail1: free(mystruct); 

一个真实世界的例子会更复杂,可能涉及多层次的结构嵌套,链表等。请注意这里free(mystruct->data); 如果第一个malloc失败,则无法调用(因为取消引用mystruct的元素无效)。

如果你想在没有goto情况下这样做,这里有一个可以很好地扩展的方法:

 char *function(char *param) { int status = 0; // valid is 0, invalid is 1 char *result = NULL; char *mem = NULL: struct binder* b = NULL; struct file* f = NULL: // check function parameter(s) for validity if (param == NULL) { status = 1; } if (status == 0) { mem = get_memory(100); // first allocation if (!mem) { status = 1; } } if (status == 0) { b = get_binder('regular binder'); // second allocation if (!b) { status = 1; } } if (status == 0) { f = mk_file(); // third allocation if (!f) { status = 1; } } if (status == 0) { // do some useful work // assign value to result } // cleanup in reverse order free(f); free(b); free(mem); return result; }