Following are examples for goto:
1. Releasing allocated memory
2. Solution for deeply nested code
3. Breaking the structured code by goto 4. Abuse of goto
1. Releasing allocated memory at the end of function
int function(...) { int ret = ERR_NONE; type *ptr = NULL; ... /* ptr points to an allocated memory block */ ret = function_a(ptr); if (ret != ERR_NONE) { report_error_log(ret); free(ptr); return ret; } ret = function_b(ptr); if (ret != ERR_NONE) { report_error_log(ret); free(ptr); return ret; } ... return ret; }
You have to write duplicate free for ptr
We can use goto to release at the end of function once error occurs
int function(...)
{
int ret = ERR_NONE;
type *ptr = NULL;
... /* ptr points to to an allocated memory block */
ret = function_a(ptr);
if (ret != ERR_NONE)
{
report_error_log(ret);
goto _exit;
}
/* implicit else */
ret = function_b(ptr);
if (ret != ERR_NONE)
{
report_error_log(ret);
goto _exit;
}
/* implicit else */
...
report_function_done_log();
_exit:
free(ptr);
return ret;
}
It's pretty much the same when there is ptr and ptr2
By the way, if 3 or above pointers are used in a function,
It's time to think about if part of statements can be extracted to sub function.
2. Solution for deeply nested code
int function(...) { int ret = ERR_NONE; type *ptr = NULL; ... /* ptr points to to an allocated memory block */ ret = function_a(ptr); if (ret == ERR_NONE) { ret = function_b(ptr); if (ret == ERR_NONE) { ... /* in some nested block */ report_function_done_log(); } else { report_error_log(ret); free(ptr); return ret; } } else { report_error_log(ret); free(ptr); return ret; } return ret; }
Deeply nested code layout is less readable for maintainability comparing to style in example 1., it's more readable if adopting goto-style of example 1.
3. Breaking the structured code by goto
Using goto to break a for loop is not safe
Following example breaks the one-in-one-out rule
Unexpected hard-to-debug behavior may occur in this style in unfortunate situation
int ret = ERR_NONE;
int i = 0;
for (; i < N; i++)
{
ret = function_x(i);
if (ret)
{
report_error_log(ret);
goto _exit;
}
}
...
_exit:
do_something();
return ret;
Following style keeps one-in-one-out structure
Using break instead of goto to leave loop
At exit of loop, checking return code and goto exit if error occurs
In this way the for loop can even be extracted to sub function
int ret = ERR_NONE;
int i = 0;
for (; i < N; i++)
{
ret = function_x(i);
if (ret)
{
report_err_log();
break;
}
}
if (ret != ERR_NONE)
goto _exit;
...
_exit:
do_something();
return ret;
4. Abuse of goto
Abuse of goto causes redundant behavior
Following code fails to allocate memory for ptr
No need to free null ptr
int function(...)
{
int ret = ERR_NONE;
type *ptr = NULL;
ptr = (type *)malloc(sizeof(type));
if (!ptr)
{
ret = errno;
report_error_log(ret);
goto _exit;
}
...
_exit:
free(ptr);
return ret;
}
So goto can be reduced
ptr = (type *)malloc(sizeof(type));
if (!ptr)
{
ret = errno;
report_error_log(ret);
return ret;
}
In conclusion goto is two-edged sword, depends on how we use it