开发C/C++模块时,因为很多内存资源都需要自己释放,为了统一一个地方释放资源通常用goto标签在函数退出时释放资源,好处是资源统一释放,不会因为在提前return时分别释放资源导致以后修改代码遗漏释放某些资源导致死锁或者内存泄漏。
以下是goto模式
void goto_function() {
char *buf = NULL;
buf = (char *)malloc(64 * sizeof(char));
if (NULL == buf) {
goto LABEL_EXIT;
}
snprintf(buf, 64, "Hello goto function");
printf("%s\n", buf);
LABEL_EXIT:
if (NULL != buf) {
free(buf);
buf = NULL;
}
return;
}
这是比较简单的goto代码,所以看起来没那么别扭。假如我们用在修改代码如下:
void goto_function() {
char *buf = NULL;
buf = (char *)malloc(64 * sizeof(char));
if (NULL == buf) {
goto LABEL_EXIT;
}
size_t len = 0; // 在goto 的label第一次使用之后,声明临时变量
snprintf(buf, 64, "Hello goto function");
len = strlen(buf);
printf("%s\n", buf);
LABEL_EXIT:
if (NULL != buf) {
free(buf);
buf = NULL;
}
return;
}
编译会出现如下错误
$ g++ main.cpp
main.cpp:9:3: error: cannot jump from this goto statement to its label
goto LABEL_EXIT;
^
main.cpp:11:9: note: jump bypasses variable initialization
size_t len = 0; // 在goto 的label第一次使用之后,声明临时变量
^
1 error generated.
如果在goto第一次使用之后,那么不能继续创建临时变量了。因为label不知道在label第一次使用之后的临时变量。要修改需要把临时变量的声明放在goto label之前
void goto_function() {
size_t len = 0; // 在goto 的label第一次使用之前,声明临时变量
char *buf = NULL;
buf = (char *)malloc(64 * sizeof(char));
if (NULL == buf) {
goto LABEL_EXIT;
}
snprintf(buf, 64, "Hello goto function");
len = strlen(buf);
printf("%s\n", buf);
LABEL_EXIT:
if (NULL != buf) {
free(buf);
buf = NULL;
}
return;
}
再编译就相安无事。
变量在函数头集体申明其实非常不利于代码的可读性,不利于查看临时变量的类型,增加冗余代码行,不能尽量让局部变量变量声明和定义放在一处执行,为了避免这种情况,我们需要规避goto带来的弊端,可以用 do while配合break
void none_goto_function() {
char *buf = NULL;
do {
buf = (char *)malloc(64 * sizeof(char));
if (NULL == buf) {
break;
}
snprintf(buf, 64, "Hello none goto function");
size_t len = 0; // 在do while内声明临时变量
len = strlen(buf);
printf("%s\n", buf);
} while(0);
if (NULL != buf) {
free(buf);
buf = NULL;
}
return;
}
这里利用了do while的break特性降解goto带来的无法邻近声明局部变量的弊端。不过只是暂时规避而已,因为break也是有缺点的,当do while内的循环或者switch也用到了break,这时候也会降低break程序的可读性。例如:
void none_goto_nest_break_function() {
char *buf = NULL;
do {
buf = (char *)malloc(64 * sizeof(char));
if (NULL == buf) {
break; // break out of do while
}
for (int i = 0; i < 5; i++) {
if (i == 3) {
break; // break out of for loop
}
}
snprintf(buf, 64, "Hello none goto function");
printf("%s\n", buf);
} while(0);
if (NULL != buf) {
free(buf);
buf = NULL;
}
return;
}
对付这种情况可以尽量将for循环内的代码用函数封装,大部分循环内代码都能够抽象为一个简单的函数。
PS: 不过抽象为函数又会影响执行性能,总之也不是终极解决方案。