This is one of few scenarios where it is perfectly acceptable (if not desirable) to use goto
.
In functions like this, there will often be resources that are allocated or state changes that are made mid-way which need to be undone before the function exits.
The usual problem with return-based solutions (e.g., rexcfnghk's and Gerrie Schenck's) is that you need to remember to undo those state changes before every return. This leads to code duplication and opens the door to subtle errors, especially in larger functions. Do not do this.
CERT actually recommends a structural approach based on goto
.
In particular, note their example code taken from copy_process
in kernel/fork.c
of the Linux kernel. A simplified version of the concept is as follows:
if (!modify_state1(true))
goto cleanup_none;
if (!modify_state2(true))
goto cleanup_state1;
if (!modify_state3(true))
goto cleanup_state2;
// ...
cleanup_state3:
modify_state3(false);
cleanup_state2:
modify_state2(false);
cleanup_state1:
modify_state1(false);
cleanup_none:
return;
Essentially, this is just a more readable version of the "arrowhead" code that doesn't use unnecessary indentation or duplicate code. This concept can easily be extended to whatever best suits your situation.
As a final note, especially regarding CERT's first compliant example, I just want to add that, whenever possible, it is simpler to design your code so that the cleanup can be handled all at once. That way, you can write code like this:
FILE *f1 = null;
FILE *f2 = null;
void *mem = null;
if ((f1 = fopen(FILE1, "r")) == null)
goto cleanup;
if ((f2 = fopen(FILE2, "r")) == null)
goto cleanup;
if ((mem = malloc(OBJSIZE)) == null)
goto cleanup;
// ...
cleanup:
free(mem); // These functions gracefully exit given null input
close(f2);
close(f1);
return;