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
/fclose
やmalloc
/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_ptr
のdeleter
を使うことで
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の実装例を示していく。