【问题标题】:When a function returns, how to close resource handles in C?当函数返回时,如何关闭 C 中的资源句柄?
【发布时间】:2018-04-29 11:30:52
【问题描述】:

如果我打开了一个文件句柄,但是函数遇到了其他不是文件错误的错误,那么在关闭句柄之前,文件句柄会不会泄露?

FILE* fp = fopen("test.txt", "r");
if (other_condition) {
    perror("other error occurred!");
    return EXIT_FAILURE;
}

如何使用C实现RAII模板?

【问题讨论】:

  • 不好的例子 - 函数返回是因为你无法获取资源,所以没有什么可以泄露的。
  • @John3136 立即更改。
  • 嗯,goto 怎么样
  • @potato 代码风格不好?一个函数包含进程和错误句柄
  • C 中没有自动 RAII 习语; is 是 C++ 的一个选项。 C 中没有自动析构函数。您必须编写代码。您没有显示对fooen() 的错误检查;那是不好的风格。您在成功返回之前不显示fclose()。您需要在错误返回时使用 fclose()

标签: c


【解决方案1】:

在 C 中,错误检查应该手动编写。 (然而,在 C++ 中,一旦对象过期,就会自动调用析构函数。)

FILE* fp = fopen("test.txt", "r");
if (fp == NULL) { file_open_error_code; } // Recommended
if (other_condition) {
    fclose(fp); // Manually close it
    perror("other error occurred!");
    return EXIT_FAILURE;
}

【讨论】:

    【解决方案2】:

    对于像文件句柄这样的单个资源,请考虑:

    int rc = EXIT_FAILURE;
    FILE *fp = fopen(file, "r");
    if (fp != NULL)
    {
        rc = function_using_resource(fp /* , … */);
        fclose(fp);
    }
    else
    {
        perror(file);
    }
    return rc;
    

    这仅在成功分配资源时使用,并从使用分配资源的函数返回状态。该函数打开文件;它负责关闭它。如果它被打开,它会关闭它。

    这样做的缺点是你最终会得到很多单独不是很大的函数。但是,将其扩展到多个资源是困难的。您可能最终会使用goto,但它是一种精心设计的使用方式。例如,考虑打开文件流并分配两个内存块的代码。你可能会得到如下代码:

    int function_example(int num_items)
    {
        int rc = EXIT_FAILURE;
        struct WotNot *wotsit = NULL;
        struct Havoc  *wreaker = NULL;
        FILE *fp = fopen(file, "r");
    
        if (fp == NULL)
        {
            err_remark("failed to open file '%s' for reading\n", file);
            goto cleanup;
        }
    
        if ((wotsit = malloc(num_items * sizeof(*wotsit))) == NULL)
        {
            err_remark("failed to allocated %zu bytes of memory\n",
                       num_items * sizeof(*wotsit));
            goto cleanup;
        }
    
        if ((rc = function_using_file(fp, num_items, wotsit)) != EXIT_SUCCESS)
        {
            err_remark("processing of wotsits failed\n");
            goto cleanup;
        }
    
        if ((wreaker = malloc(num_items * 2 * sizeof(*wreaker)) == NULL)
        {
            err_remark("failed to allocated %zu bytes of memory\n",
                       num_items * 2 * sizeof(*wreaker));
            goto cleanup;
        }
    
        if ((rc = function_wreaking_havoc(num_items, wotsit, wreaker) != EXIT_SUCCESS)
        {
            err_remark("failed to wreak appropriate havoc\n");
            goto cleanup;
        }
    
        if ((rc = other_function(fp, num_items * 2, wreaker)) != EXIT_SUCCESS)
        {
            err_remark("failed to process the wotsits — not enough havoc?\n");
            goto cleanup;  // Systematic, but nominally superfluous
        }
    
    cleanup:
        free(wreaker);
        free(wotsit);
        if (fp != NULL)
            fclose(fp);
        return rc;
    }
    

    注意资源如何被初始化为无害状态(空指针)。如果有问题,代码会跳转到清理块。 free() 函数优雅地接受一个空指针并且什么都不做,所以这些调用不需要检查,但是fclose() 在给定空指针的情况下不一定能干净地工作,所以在调用fclose() 之前检查文件流。

    这是一个相当普遍的成语。变体包括用于不同数量的清理工作的多个标签。当您没有在此函数中放置太多活动来代替显示的函数调用时,这仍然是最清楚的。然而,除了资源分配之外,在函数中完成的工作并不少见——通常包括工作负载的计算部分,尤其是在涉及诸如动态增长一个或多个分配的数组之类的工作时。像这样混合起来仍然不是一个特别好的主意,但这并不能阻止人们这样做。

    您应该对被调用函数报告错误或调用函数报告错误或两者都不报告有一个约定。您还可以使用各种机制来构建错误报告。确保尽可能轻松地处理错误很重要——这意味着人们不太可能忽略错误报告。

    其他一些相关的问题包括:

    等等

    【讨论】:

    • 我的意思是当出现其他错误时,不是文件问题,让程序崩溃。那时,没有时间关闭
    • 如果你的程序崩溃了,你就不用清理了。在 RAII 是系统不可或缺的 C++ 中也是如此。崩溃意味着“失控”。如果你在控制之下停下来,我所展示的内容几乎是最好的,是的,它确实是那么冗长。
    【解决方案3】:

    如果我打开一个文件句柄,但函数在关闭句柄之前返回,文件句柄是否会泄露?

    是的。

    如何使用C实现RAII模板?

    C 中没有 RAII,也没有办法以可移植的方式实现它。 OTOH,它的重要性不那么重要,因为在 C 中,所有控制流(longjmp 除外)都是显式的。不存在由异常触发的堆栈展开之类的事情,您的函数仅在您明确return 时返回,因此您可以(并且应该)在所有返回点明确清理。

    在实践中,如果您有一个获取资源的代码块(因此如果出现故障或最后必须清理),那么在最后有单个清理/退出点和goto 是一种常见模式失败时给它(加上标记值以标记尚未初始化的值;另一个常见的选择是严格按照初始化的相反顺序进行清理,并根据成功的初始化量跳到多个“入口点”,但 IMO 很快就会变得一团糟)。

    int copy_file(const char *src, const char *dest) {
        int ret = -1;
        FILE *in = NULL;
        FILE *out = NULL;
        char buf[1024];
        size_t rb;
        if((in = fopen(src, "rb")) == NULL) goto cleanup;
        if((out = fopen(dest, "wb")) == NULL) goto cleanup;
        for(;;) {
            rb = fread(buf, 1, sizeof(buf), in);
            if(rb == 0) break;
            if(fwrite(buf, 1, rb, out) != rb) break;
        }
        ret = 0;
        if(ferror(in)) ret = -2;
        else if(ferror(out)) ret = -3;
    cleanup:
        if(in) fclose(in);
        if(out) fclose(out);
        return ret;
    } 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 1970-01-01
      • 2011-10-27
      相关资源
      最近更新 更多