For a single resource like a file handle, consider:
int rc = EXIT_FAILURE;
FILE *fp = fopen(file, "r");
if (fp != NULL)
{
rc = function_using_resource(fp /* , … */);
fclose(fp);
}
else
{
perror(file);
}
return rc;
This only uses the resource if it was successfully allocated, and returns the status from the function that uses the allocated resource. This function opened the file; it is responsible for closing it. It does close it if it was opened.
The perceived downside to this is that you end up with a lot of functions which are individually not very big. However, extending this to multiple resources is hard. You probably end up using a goto
, but it is a carefully structured use. For example, consider code that opens a file stream, and allocates two blocks of memory. You might end up with code like:
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;
}
Note how the resources are initialized to a harmless state (null pointers). If there's a problem, the code jumps to the cleanup block. The free()
function accepts a null pointer gracefully and does nothing, so those calls don't need a check, but fclose()
does not necessarily work cleanly given a null pointer, so the file stream is checked before calling fclose()
.
This is a fairly common idiom. Variations include multiple labels for different amounts of cleanup work. This still tends to be clearest when you don't put too much activity in this function in place of the function calls shown doing the work. However, it is not uncommon to see work done in the function other than resource allocation — the computation part of the workload is often included, especially if there's work involved such as dynamically growing one or more of the allocated arrays. It still isn't a particularly good idea to mix things up like that, but that doesn't stop people doing it anyway.
You should have a convention for whether the called function reports the errors or the calling function reports errors, or neither. You can also use various mechanisms for structuring error reporting. Making sure that handling errors is as painless as possible is important — it means people are less likely to skimp on the error reporting.
Other questions of some relevance include:
Etc.