【C言語】
goto文を使ったfclose問題対策

warning: double ‘fclose’ of FILE ‘fp’
warning: leak of FILE ‘fp’

警告: fclose多すぎ、少なすぎ
[-Wanalyzer-double-fclose]
[-Wanalyzer-file-leak]


■fcloseが多すぎるバグ例

//gcc -fsanitize=leak   動的解析
//gcc -fanalyzer        静的解析
#include    <stdio.h>
int main(void) {
    FILE    *fp1 = fopen("/usr/include/stdio.h","re");
    if(fp1 == NULL){
        return  1;
    }
    FILE    *fp2 = fopen("テスト用(存在しないファイル)","re");
    if(fp2 == NULL){
        //異常時処理
        perror(NULL);
        fflush(0);
        fclose(fp1);
        //処理続行
    }
    
    //正常時処理
    //異常時と正常時の2回 fclose() してしまった例
    fclose(fp1) ;   
    fclose(fp2) ;
}

同じファイルポインタを2回fclose() してしまった問題です。
異常時処理でfclose() しているのに正常処理でもfclose() してしまう場合が多いです。

この問題は以下のオプションで検出できます。
gcc -fsanitize=leak 動的解析
gcc -fanalyzer 静的解析


■fcloseが少なすぎるバグ例

#include    <stdio.h>
int f(void){
    FILE    *fp1 = fopen("/usr/include/stdio.h","re");
    if(fp1 == NULL) {
        return 1;
    }
    FILE    *fp2 = fopen("テスト用(存在しないファイル)","re");
    if(fp2 == NULL){
        return 1;   //問題:fclose(fp1) 忘れ
    }
    //  処理色々~~~~~
    //  後始末
    fclose(fp1);
    fclose(fp2);
    return 0;
}
int main(void){
    for(int i = 0;i < 1000;i++){
        f();
    }
}

■goto文を使ったfclose問題対策(推奨)

#include    <stdio.h>
int main(void) {
    FILE    *fp1 = NULL ;
    FILE    *fp2 = NULL ;
    
    fp1 = fopen("/usr/include/stdio.h","r");
    if(fp1 == NULL){
        goto err;
    }
    fp2 = fopen("テスト用(存在しないファイル)","r");
    if(fp2 == NULL){
        goto err;   
    }
    //
    //処理色々
    //
err:
    //クリーンナップ処理
    if(fp1) fclose(fp1) ; 
    if(fp2) fclose(fp2) ;
} 

関数末尾に資源解放の
クリーンナップ処理をまとめ、
そこへgoto で飛ぶ手法を推奨します。

注意事項:

fp1,fp2は関数の先頭でNULLで初期化する必要があります。
C99式に使用する直前で変数宣言すると
クリーンナップ処理で未初期化変数を参照する可能性があります。


参考:

MEM12-C. リソースの使用および解放の最中に発生するエラーが原因で関数を終了する場合に、Goto 連鎖の使用を検討する

CWE-1341