c c++函數資源釋放時避免goto的方法
開發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: 不過抽象爲函數又會影響執行性能,總之也不是終極解決方案。