在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; }