今天我在維護一個C項目的時候發現有的函數內部有很多的return語句,每個return前面都有一段相同的資源釋放的代碼。代碼看起來就像這樣:
int f() {
char *p1 = (char*)malloc(1024 * sizeof(char));
int *p2 = (int*)malloc(10 * sizeof(int));
...
if (...) {
free(p1);
free(p2);
return 1;
}
for (...) {
if (...) {
free(p1);
free(p2);
return 2;
}
}
...
free(p1);
free(p2);
return 0;
}
上面的代碼資源釋放部分重復度太高,讀起來不夠清爽。更重要的是如果某一個return之前忘記了釋放資源就會發生內存洩露,只能靠程序的作者和維護者隨時提高警惕。這類問題在C++中可以通過RAII來很好地解決,但可惜C語言沒有自動析構機制,所以,我們需要通過一些技巧來達到類似的效果。看下面的代碼:
int void f() {
int return_code = 0;
char *p1 = (char*)malloc(1024 * sizeof(char));
int *p2 = (int*)malloc(10 * sizeof(int));
...
if (...) {
return_code = 1;
goto _END_F;
}
for (...) {
if (...) {
return_code = 2;
goto _END_F;
}
}
...
_END_F:
free(p1);
free(p2);
return return_code;
}
我們把資源釋放代碼統一放在函數末尾,並打上_END_F標簽,把原來return的地方用goto _END_F替換。我們通過這種方式消除了資源釋放部分的冗余代碼,也一定程度上減小了忘記釋放資源的風險。不過,由於現在需要每次在goto前面加上return_code=x,還是有一定的冗余和遺忘的危險。於是,我們可以通過下面的宏可以進一步完善:
#define RETURN(label, returnCode) { return_code = returnCode; goto label;}
這樣我們就可以用RETURN(_END_F, 2)來一並實現設置返回值和goto的效果了,即簡化了代碼,又防止忘記設置返回值。 最終重構效果如下:
#define RETURN(label, returnCode) { return_code = returnCode; goto label;}
int void f() {
int return_code = 0;
char *p1 = (char*)malloc(1024 * sizeof(char));
int *p2 = (int*)malloc(10 * sizeof(int));
...
if (...) {
RETURN(_END_F, 1);
}
for (...) {
if (...) {
RETURN(_END_F, 2);
}
}
...
_END_F:
free(p1);
free(p2);
return return_code;
}