funini.com 自由研究 C/C++ エラー処理

エラー処理

C 言語には、try ... catch みたいな便利な仕組みがありません。 だから、エラー処理を含むコードはちょっと工夫が必要です。

問題

関数 f() を呼ぶためには、A, B, C の三つの構造体を用意する必要があります。 それぞれ、初期化処理と終了処理が必要です。 エラー処理を含まないコードはこんな感じ。
void do_f(){
  struct A *a;
  struct B *b;
  struct C *c;

a = create_a(); b = create_b(); c = create_c();

f(a,b,c);

free_c(c); free_b(b); free_a(a); }

create_a() は中で malloc() を呼んで、初期化を行い、a のアドレスを返します。 ただし初期化でエラーが発生すると、NULL を返します。
a,b は無事確保できたのに、c の確保に失敗した場合は、 メモリリークしないように a, b を解放しなければいけません。

簡単に思いつく回答

void do_f(){
  struct A *a;
  struct B *b;
  struct C *c;

a = create_a(); if(a == NULL) return; b = create_b(); if(b == NULL){ free_a(a); return; } c = create_c(); if(c == NULL){ free_b(b); free_a(a); return; }

f(a,b,c);

free_c(c); free_b(b); free_a(a); }

意味的には OK ですが、free_a() を色んなところに書かないといけなかったりして、あまりきれいではありません。

goto を使う

void do_f(){
  struct A *a;
  struct B *b;
  struct C *c;

a = create_a(); if(a == NULL) return; b = create_b(); if(b == NULL) goto free_a; c = create_c(); if(c == NULL) goto free_c;

f(a,b,c);

free_c(c); free_b: free_b(b); free_a: free_a(a); }

goto を使うことで、かえってコピペの少ない、きれいなコードになっています。 資源の確保と解放が逆順になっているのがポイント。
(a,b,c の順で確保し、c,b,a の順で解放)

do .. while(0) を使う

ちょっと奥の手で、do {} while (0); を使うと、ブロックを途中で抜けることが出来ます。
void do_f(){
  struct A *a = NULL;
  struct B *b = NULL;
  struct C *c = NULL;

do { a = create_a(); if(a == NULL) break; b = create_b(); if(b == NULL) break; c = create_c(); if(c == NULL) break;

f(a,b,c); } while(0);

if(c) free_c(c); if(b) free_b(b); if(a) free_a(a); }

ループのようですが、while の条件が 0 (偽)なので、実は一回しか実行されません。
最後のところでは a, b, c をフラグのように使っています。初期化で NULL を代入しているのもポイント。