对于像文件句柄这样的单个资源,请考虑:
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() 之前检查文件流。
这是一个相当普遍的成语。变体包括用于不同数量的清理工作的多个标签。当您没有在此函数中放置太多活动来代替显示的函数调用时,这仍然是最清楚的。然而,除了资源分配之外,在函数中完成的工作并不少见——通常包括工作负载的计算部分,尤其是在涉及诸如动态增长一个或多个分配的数组之类的工作时。像这样混合起来仍然不是一个特别好的主意,但这并不能阻止人们这样做。
您应该对被调用函数报告错误或调用函数报告错误或两者都不报告有一个约定。您还可以使用各种机制来构建错误报告。确保尽可能轻松地处理错误很重要——这意味着人们不太可能忽略错误报告。
其他一些相关的问题包括:
等等