C言語でもdeferを使いたい #1

Table of Contents

C言語でファイル操作やmutexを複数使っていると go言語のようにdeferを使いたくなることがある。

仮に3つ開放処理が必要な操作do_somethingをするとした場合、 C言語で書くと次のようになる。

int hoge(){
  int lock1=lock();
  if (!lock1){
    return 1;
  }

  int lock2=lock();
  if (!lock2){
    unlock1(lock1);
    return 1;
  }

  int lock3=lock();
  if (!lock3){
    unlock2(lock2);
    unlock1(lock1);
    return 1;
  }

  do_something();

  unlock(lock3);
  unlock(lock2);
  unlock(lock1);
  return 0;
}

Note

関数lock/unlockは、存在しない仮置きの関数です。 実際にはfopen/fclosemalloc/free, pthread_mutex_lock/pthread_mutex_unlockといった関数に置き換えてください。

これをgo言語で書くと、まとめて処理化処理とその開放処理を書ける。

func hoge() error{
  lock1,err:=lock();
  if err!=nil{
    return err
  }
  defer unlock(lock1);

  lock2,err:=lock();
  if err!=nil{
    return err
  }

  defer unlock(lock2);
  lock3,err:=lock();
  if err!=nil{
    return err
  }
  do_something()

  defer unlock(lock3);
  return nil
}

go言語と同様の書き方をなんとかC言語で実現することを考える。

Note

C++の場合は、クラスのデストラクタを活用してRAIIとして実装すれば 容易に開放処理を記述できます。 また、既存のコードに対してもstd::unique_ptrdeleterを使うことで golangのdeferと同様の記載をすることができます。

まず、初期化処理と開放処理はペアになっていることから 関数ポインタのスタックを利用することで実現できる。 構造体の例を示すと次のようになる。

#include <stddef.h>
typedef struct defer_item{
  void (*deleter)(void* context);
  void *context;
}defer_item_t;

typedef struct defer_impl{
  size_t len,cap;
  defer_item_t *items;
  //reallocで伸び縮みさせる。
  //リスト構造はミスキャッシュの確率が上がるため利用しない。
}defer_impl_t;

また、必要な関数を示すと次にようになる。

//FILEのように実装を隠蔽する
typedef struct defer_impl_t* defer_handle;
//nullableなdefer_handleを返す
defer_handle defer_open();
//defer_handleに登録されている開放処理をまとめて呼び出す。
void defer_close(defer_handle*);

// 開放処理を登録する
int defer_push(defer_handle, void(*deleter)(void*),void* context);
//型を消去してコンパイルエラーを抑制するヘルパーマクロ
#define CAST_CALLBACK(f) ((void(*deleter)(void*))(f)) 

あると便利かもしれない関数も示すと次のようになる。


//先頭の要素を開放する(多分あまり使わない)
void defer_pop(defer_handle);

// 開放処理のサブセット
size_t defer_depth(defer_handle);
void defer_pop_until(defer_handle,size_t depth);

これを使うとC言語でも次のような記述ができるはずだ。

int hoge(){
  defer_handle defer=defer_open();
  if (!defer){
    return 1;
  }
  
  int lock1=lock();
  if (!lock1){
    defer_close(defer);
    return 1;
  }
  defer_push(defer, unlock, lock1);

  int lock2=lock();
  if (!lock2){
    defer_close(defer);
    return 1;
  }
  defer_push(defer, unlock, lock2);

  int lock3=lock();
  if (!lock3)return{
    defer_close(defer);
    return 1;
  }
  defer_push(defer, unlock, lock3);
  do_something();

  defer_close(defer);
  return 0;
}

Warning

開放処理ではエラーが起きないものと仮定しているため、 エラーが起きた場合は正しくエラーハンドリングできず、 exit/abort等でアプリケーションを終了する必要がある。

次回は、C言語版deferの実装例を示していく。