【问题标题】:Free memory in a loop regardless of return code无论返回码如何,都在循环中释放内存
【发布时间】:2020-06-29 16:10:33
【问题描述】:

我有以下 C 伪代码:

int main()
{
    int rc = 0;

    for (int i = 1; i < 10; i++) {
        char *data = malloc(i); // Each iteration has a different alloc size

        rc = do_something(data);
        if (rc != 0) goto cleanup;

        rc = do_something_else();
        if (rc != 0) goto cleanup;

cleanup:
        free(data);
        if (rc != 0) break;
    }
    return rc;
}

如果被调用的函数返回错误,我想模拟 Python 的 try...finally 模式,但前提是必须进行一些必要的清理工作。

到目前为止,代码对我来说看起来不错,但我是 C 新手。是否有不同的模式可以避免 free 之后重复的 rc != 0 测试? (我知道有些人认为goto 无条件错误,但我认为这是我为这种情况找到的最干净的解决方案。)

【问题讨论】:

  • 您可以将rc != 0 添加到for 循环中:for (int i = 0; i &lt; 10 &amp;&amp; rc != 0; i++)...只需初始化rc=1; 而不是0。在这种情况下,您将不需要break跨度>
  • @AlexLop。不错!

标签: c loops error-handling goto


【解决方案1】:

在您展示的特定代码/案例中,您可以通过使用 realloc 函数而不是 malloc 来消除任何类型的每个循环清理的需要。这将简单地用一个新的块(不同大小)替换在前一个循环(如果有的话)上分配的内存。然后,您可以简单地将任何清理(即调用 free)推迟到循环之外。

当发生错误时,您仍然可以使用break 语句退出循环;或者,如 cmets 中所述,您可以在循环条件中添加 rc != 0 测试。

这里有一些 C 代码,可以按照我的指示进行操作(当然,您需要两个调用函数的实际定义才能使其工作):

#include <stdlib.h>
int do_something(char* data);
int do_something_else(void);

int main()
{
    int rc = 0;
    char* data = NULL;
    for (size_t i = 1; i < 10; i++) {
        char* temp = realloc(data, i); // If "data" is NULL (1st loop) -> same as malloc
        if (temp == NULL) { // Allocation failure...
            // Error message
            break;
        }
        data = temp; // Succesfull allocation, so update "data" pointer

        rc = do_something(data);
        if (rc != 0) break;

        rc = do_something_else();
    }
    free(data); // We now only have to do the cleanup once (calling with NULL is allowed).
    return rc;
}

请随时要求进一步澄清和/或解释。

【讨论】:

  • 这比我的回答还要好。
  • 这非常吸引人,可能适用于我的 real 代码,其中 alloc 和 free 调用位于单独的函数中,但它提出了一个更广泛的问题:是否有任何问题在被调用函数中用 realloc 替换 malloc,这是通用的,并且在广泛的上下文中被调用?
  • @user3758232 如果你总是有一个malloc ... free循环模式,那么用realloc(在循环中)和一个free替换它之后循环不会改变整体行为模式(只要采取相关预防措施来防止对 realloc 的失败调用,正如我在代码中所示)。
  • @user3758232 另外,如果您将指针传递给使用realloc 的函数,那么您必须传递一个 pointer-to-pointer 以获得修改后的值返回给调用者。但是,当您使用malloc 时,可能也是这种情况。
【解决方案2】:

您可以在循环外声明变量。然后,只要你释放它,就将变量设为空,并在循环后检查它。

    char *data = NULL;
    for (int i = 1; i < 10; i++) {
        data = malloc(i); // Each iteration has a different alloc size

        rc = do_something(data);
        if (rc != 0) break;

        rc = do_something_else();
        if (rc != 0) break;

        free(data);
        data = NULL;
    }
    if (data) {
        free(data);
    }

【讨论】:

    【解决方案3】:

    在需要资源管理并且需要执行一组检查的情况下使用这种模式(goto)是可以的,而每次失败都需要停止流并执行所需的清理。

    在我看来,为了使代码更简洁,将rc != 0 条件放在for 循环中:

    int rc = 1;
    for (int i = 0; i < 10 && rc != 0; i++)
    {
        ...
    

    在这种情况下,您根本不需要break,因为一旦将rc 与0 进行比较,循环就会停止。我还建议检查malloc 的结果:

    int foo(void)
    {
        int rc = 1;
    
        for (int i = 1; i < 10 && rc != 0; i++) {
            char *data = malloc(i); // Each iteration has a different alloc size
            if (!(rc = !!data)) goto cleanup;
    
            rc = do_something(data);
            if (rc != 0) goto cleanup;
    
            rc = do_something_else();
            if (rc != 0) goto cleanup;
    
    cleanup:
            free(data);
        }
    
        return rc;
    }
    

    请注意,free NULL 指针是完全合法的。

    【讨论】:

    • 我已经接受了一个答案,但这实际上是最通用的一个,因为它适用于并非特定于 alloc / realloc 或我无法修改该代码的情况。跨度>
    【解决方案4】:

    根据我的经验,对于这种 C 语言中的错误处理问题,很少有真正简单的解决方案。

    你可以做这样的事情,但如果这真的更好,这是有争议的。

    int main()
    {
        int rc = 0;
    
        for (int i = 1; i < 10; i++) {
            char *data = malloc(i); // Each iteration has a different alloc size
    
            rc = do_something(data);
            if (rc != 0) goto cleanup;
    
            rc = do_something_else();
            if (rc != 0) goto cleanup;
            
            free(data);
            continue;
    
    cleanup:
            free(data);
            break;
        }
        return rc;
    }
    

    【讨论】:

      猜你喜欢
      • 2011-11-14
      • 2010-10-30
      • 1970-01-01
      • 2011-12-23
      • 1970-01-01
      • 1970-01-01
      • 2012-10-31
      • 2019-09-12
      • 2016-02-19
      相关资源
      最近更新 更多