

C言語で複数のファイルを開いて使う処理を書く場合、  
エラー処理はどのように書くのがきれいでしょうか。

以下のような形を考えましたが、2つ目のファイルオープンで  
エラーになったときの処理が気持ち悪いです。

[code]

    int func1(char *fname1, char *fname2)
    {
        FILE *fp1;
        FILE *fp2;
    
        if((fp1 = fopen(fname1, "w")) == NULL)
        {
            fprintf(stderr, "fname1 open error.\n");
            return -1;
        }
        if((fp2 = fopen(fname2, "w")) == NULL)
        {
            fprintf(stderr, "fname2 open error.\n");
            fclose(fp1);    //ここが気持ち悪い
            return -1;
        }
        int ret;
        ret = func2(fp1, fp2);  //ファイルに書き込む処理
        fclose(fp1);
        fclose(fp2);
        if(ret != 0)
        {
            fprintf(stderr, "error occured in func2.\n");
            return -1;
        }
        return 0;
    }
    
[/code]


CC BY-SA 3.0
2015-02-12T15:03:50.873
6397
2015-02-13T01:54:41.837
4696
question
c
C言語で複数のファイルを開くときのエラー処理


こういう場合では `goto` を使うと綺麗に書けるので、使用して問題なければ以下のように書くのはどうでしょうか？

[code]

    int func1(char *fname1, char *fname2)
    {
        int r = 0;    // 返り値に使用
        FILE *fp1 = NULL;
        FILE *fp2 = NULL;
    
        if((fp1 = fopen(fname1, "w")) == NULL)
        {
            fprintf(stderr, "fname1 open error.\n");
            r = -1;
            goto cleanup;
        }
        if((fp2 = fopen(fname2, "w")) == NULL)
        {
            fprintf(stderr, "fname2 open error.\n");
            r = -1;
            goto cleanup;
        }
        int ret;
        ret = func2(fp1, fp2);  //ファイルに書き込む処理
        if(ret != 0)
        {
            fprintf(stderr, "error occured in func2.\n");
            r = -1;
            goto cleanup;
        }
    
    cleanup:
        if(fp1 != NULL){
            fclose(fp1);
        }
        if(fp2 != NULL){
            fclose(fp2);
        }
        return r;
    }
    
[/code]


CC BY-SA 3.0
2015-02-12T15:17:21.350
6398
2015-02-12T15:17:21.350
5106
6397
answer


Kyosuke Kameda さんの回答とほぼ同じですが、クリーンアップ時に `NULL` 判定を行わないやり方もあります。

[code]

    int func1(char *fname1, char *fname2)
    {
        int r = 0;
        FILE *fp1;
        FILE *fp2;
    
        if((fp1 = fopen(fname1, "w")) == NULL)
        {
            fprintf(stderr, "fname1 open error.\n");
            r = -1;
            goto err_open1;
        }
        if((fp2 = fopen(fname2, "w")) == NULL)
        {
            fprintf(stderr, "fname2 open error.\n");
            r = -1;
            goto err_open2;
        }
        int ret = func2(fp1, fp2);  //ファイルに書き込む処理
        if(ret != 0)
        {
            fprintf(stderr, "error occured in func2.\n");
            r = -1;
            goto err_func2;
        }
    
    // cleanup
    err_func2:
        fclose(fp2);
    err_open2:
        fclose(fp1);
    err_open1:
        return r;
    }
    
[/code]

エラー判定が必要な処理 hoge 毎に

[code]

        hogeのクリーンアップ処理;
    err_hoge:
    
[/code]

というのを組にしています。  
このやり方だと、 `fp1` と `fp2` を閉じる順序がオリジナルと逆に  
する必要がありますが、経験上、ファイルやメモリなどは、開いたり確保したりしたのと逆順に解放していった方がよいことが多いです。  
オリジナルの順に閉じなければならない、という場合はこの方法が使えないのが難点かもしれません。


CC BY-SA 3.0
2015-02-12T15:46:56.650
6402
2015-02-12T15:46:56.650
7831
6397
answer


私も`goto`使いますが、どんなパターンでも`goto`使うの禁止っていう残念職場ならこう書きます。

[code]

    FILE *fp1 = fopen(fname1, "w");
    FILE *fp2 = fopen(fname2, "w");
    
    if(fp1 == NULL || fp2 == NULL)
    {
        (fp1 == NULL) ? fprintf(stderr, "fname1 open error.\n") : fclose(fp1);
        (fp2 == NULL) ? fprintf(stderr, "fname2 open error.\n") : fclose(fp2);
        return -1;
    }
    
    // ファイル処理
    ･･･
    
    fclose(fp1);
    fclose(fp2);
    
[/code]


CC BY-SA 3.0
2015-02-12T16:18:19.623
6406
2015-02-12T16:18:19.623
7214
6397
answer


正攻法は他の回答にまかせるとして、GCC独自拡張の[`cleanup`属性](https://gcc.gnu.org/onlinedocs/gcc/Variable-
Attributes.html)を利用して実装することもできます。他コンパイラでは利用できないので、利用に際してはご注意ください。

Wikipedia記事 "[Resource Acquisition Is
Initialization](http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization#GCC_.22cleanup.22_extension_for_C)"
より引用：

[code]

    static inline void fclosep(FILE **fp) { if (*fp) fclose(*fp); }
    #define _cleanup_fclose_ __attribute__((cleanup(fclosep)))
    
    void example_usage() {
      _cleanup_fclose_ FILE *logfile = fopen("logfile.txt", "w+");
      fputs("hello logfile!", logfile);
    }
    
[/code]


CC BY-SA 3.0
2015-02-13T01:54:41.837
6419
2015-02-13T01:54:41.837
49
6397
answer