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

bod*_*ydo 16 c memory free allocation

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

所以我喜欢:

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

    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢,Boda Cydo.

kar*_*lip 35

您可以定义一个可以释放资源的新标签,然后您可以在代码失败时进行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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • +1 - 虽然在学校不受欢迎,但这是最实用的,并且一直在做.在离开函数之前将所有自由语句放在标签上,必要时首先检查状态变量,然后使用GOTO. (6认同)
  • 关于这一点的主要警告是,您需要知道变量是否实际分配.如果代码在'getbinder()'调用之后跳转,则变量`f`将不会被初始化并且只会巧合地为零,因此您可能很容易最终释放垃圾,这是一个麻烦的方法.因此,您必须确保在函数顶部将任何已分配的资源初始化为零.这并不是说技术是错的; 实际上,它非常有效.不过,你必须要小心一点.如果C++是一个选项,析构函数可以自动清理. (4认同)
  • 这就是你在C中实现`异常处理'的方法. (2认同)
  • @Jonathan Leffler:解决方案是简单地有多个标签,每个初始化阶段一个。标签与初始化的顺序相反。如果初始化失败,它会跳转到最后的相应标签,跳过清理以进行稍后的初始化,并通过清理以进行较早的初始化。以前我做 C 的时候一直在使用这个技巧。 (2认同)

Mik*_*one 5

我知道我会因此而被私刑,但我有一位朋友说他们已经习惯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;
}
Run Code Online (Sandbox Code Playgroud)

顺便说一下,在块周围散布局部变量声明就像那不是标准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;
}
Run Code Online (Sandbox Code Playgroud)


t0m*_*13b 5

虽然我很佩服你的防御编码方法,这是一件好事.每个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;
    }
Run Code Online (Sandbox Code Playgroud)

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