1. 程式人生 > >函式中為什麼不要有多次return以及其解決方法

函式中為什麼不要有多次return以及其解決方法

函式中隨處return,是造成我們資源洩露和程式死鎖的主要根源。很多同志寫過類似的程式碼,函式中建立了和引用了多個資源,中間使用的過程中出錯了,程式return,經典的程式碼是這樣的:

void fun()  
{
    Lock(mutex);
    mem = malloc(size);
    if (null == mem)
    {          
        return; //死鎖     
    }     
    fh = fopen(“test.txt”);     
    if (fh)      
    {          
        return; //死鎖+記憶體洩露     
    }     
    if (fwrite(“abc”, fh) < 0)      
    {          
        return; //死鎖+記憶體洩露+控制代碼洩露     
    }     
    fclose(fh);      
    free(mem);      
    Unlock(mtex);
}

上述程式碼中的資源洩露是顯而易見的,程式設計新手卻很容易寫出這樣的程式碼,但問題卻不容易發現,因為異常流程通常不會發生,而一旦發生了,就是一場噩夢:不好跟蹤也不好重現。對吃過這種虧程式設計老手而言,處理起來會格外小心,而是有了如下版本的程式碼(我們的工程中類似的程式碼隨處可見):

void fun()  
{
    Lock(mutex);
    mem = malloc(size);     
    if (null == mem)      
    {          
        Unlock(mutex); //鎖解除了,很好         
        return;      
    }   
    fh = fopen(“test.txt”);     
    if (fh)      
    {          
        Free(mem);          
        Unlock(mutext); //錯誤處理加了一行         
        return;      
    }    
    if (fread(mem, size, fh) < 0)      
    {          
        Free(mem);          
        Flcose(fh);          
        Unlock(mutext); //錯誤處理又加了一行…         
        return;      
    }   
    fclose(fh);      
    free(mem);      
    Unlock(mtex);
}

這個版本的程式碼沒什麼邏輯問題,在每個異常環節,把該處理的都處理完了。看起來非常完美,卻依然存有大坑。首先,我們的異常處理流程越來越長,重複的程式碼像火車車廂一樣越串越長,程式碼維護非常不方便,某天其中一個異常處理流程要修改了,我們需要修同步多處,萬一漏了某處(事實上不是萬一,而是經常),問題沒有修改徹底;更為嚴重的是,冗於的異常處理會讓大家感到疲勞,某一天函式中又要加入新的資源引用或異常處理邏輯時,在先驅的引導下,我們的人依然還是會犯同樣的錯誤:要麼什麼都不管提前返回,或者處理不充分就走了,結果還是同樣的資源洩露。

 
如何解決,Linux核心程式碼給了我們很好的解決方案,合理地使用goto語句,將異常處理定向到統一的地方,既解決了資源洩露隱患,也保持了正常流程程式碼的簡潔性,這是改進後的版本:

void fun()  
{      
    Lock(mutex);   
    mem = malloc(size);     
    if (null == mem)      
    {          
        goto ERR_EXIT1;      
    }     
    fh = fopen(“test.txt”);     
    if (fh)      
    {          
        goto ERR_EXIT2;      
    }     
    if (fread(mem, size, fh) < 0)      
    {         
        goto ERR_EXIT3;      
    }   
    ERR_EXIT3:      
    fclose(fh);  
    ERR_EXIT2:      
    free(mem);  
    ERR_EXIT1:      
    Unlock(mtex);  
}

總結:以後新寫C程式碼的異常處理,統一採用上面的模版。C++/java有更好的異常處理機制,上述方案只供參考,但解決思路應該是一致的,隨處return與冗餘異常處理應該被嚴厲禁止。