2016年12月4日 星期日

Using goto safely

Goto statement is two-edged sword, if we use goto under the premise of structured programming, it's safe and more readable for maintenance.

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

沒有留言:

張貼留言