17

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

所以我喜欢:

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()事情失控的速度有多快。如果某些功能失败,我必须先释放每个分配。代码很快变得丑陋,我所做的就是复制粘贴所有内容。我成为了一名复制/粘贴程序员,更糟糕的是,如果有人在两者之间添加了一条语句,他必须修改下面的所有代码来调用free()他的添加。

有经验的 C 程序员如何解决这个问题?我什么都想不通。

谢谢,博达赛多。

4

6 回答 6

37

您可以定义一个新标签来释放资源,然后您可以在代码失败时转到它。

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 进行错误管理: Valid use of goto for error management in C?

于 2010-07-27T00:34:15.810 回答
6

我知道我会因此被处以私刑,但我有一个朋友说他们曾经这样做过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;
}
于 2010-07-27T00:42:42.790 回答
5

虽然我很欣赏您的防御性编码方法,但这是一件好事。每个C程序员都应该有这种心态,它也可以适用于其他语言......

我不得不说这是对 GOTO 有用的一件事,尽管纯粹主义者会说不然,这将相当于一个 finally 块,但我可以在那里看到一个特殊的陷阱......

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标签....这可能会引起微妙的错误,对堆上指针的所有引用都消失了,最终可能会返回垃圾......所以请确保如果您分配了内存并将其返回,请return在标签之前使用关键字,否则内存可能会消失.. . 或造成内存泄漏....

于 2010-07-27T00:53:43.520 回答
3

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

struct binder* b = get_binder('regular binder');  // second allocation
if(b) {
   struct ... *c = ...
   if(c) {
      ...
   }
   free(b);
}
于 2010-07-27T08:31:28.443 回答
2

如果您的数据结构复杂/嵌套,则单个 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);则无法调用(因为取消引用的元素mystruct无效) 。malloc

于 2010-07-27T05:54:01.153 回答
2

如果你想在没有的情况下这样做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;
}
于 2010-07-27T04:18:01.873 回答