RAIIもどき in C

gccの__attribute__((cleanup(fn))) が便利すぎる件について。


C++でコードを書くときは、RAIIとか呼ばれているイディオムを使えば、ご存じの通り、ロックしたmutexを手動で開放する必要もないですし、newしたオブジェクトを手動でdeleteする必要もないです。

void Baz::boo() const {
  boost::mutex::scoped_lock lock(mutex);
  // ...
  return; // lock変数のデストラクタで自動開放。手動での開放不要
}

でもC言語だと、当然ながら手動で開放しないといけません。複数箇所でreturnしている場合など、タイプが面倒臭すぎ。

int foo() {
  pthread_mutex_lock(&mutex);
  // ...
  if (hogehoge) {
      pthread_mutex_unlock(&mutex); // 手動...
      return;
  }
  // ...
  pthread_mutex_unlock(&mutex); // 手動...
  return;
}

ところが、GCCの__attribute__一覧を良く見たら、"cleanup" というのがありました。これは自動変数に付けるattributeで、その自動変数がスコープから外れたときに指定の関数を呼び出すことを指示するものです。このattributeを下記のように使うと、ロックしたmutexの開放を一つも書かずに自動で行うことができちゃいます。ほぼC++と同等。

#define SCOPED_LOCK(m)                                                  \
  pthread_mutex_lock(&m); /* エラー処理は略 */                          \
  void u_(pthread_mutex_t** m_) { pthread_mutex_unlock(*m_); }          \
  __attribute__((__cleanup__(u_))) pthread_mutex_t* scoped_lock_ = &m

int bar() {
  SCOPED_LOCK(mutex); // これだけでオケ
  // ...
  return 0; // 開放不要
}

このattributeは、上記の例の他、fdの自動closeにでも、mallocしたメモリの自動freeにでも、なんにでも使えます。自動で呼び出す関数は、自動変数の型に * を一個足したものを引数にとればOKデス。関数の戻り値は無視されます。同一関数内で2つ以上のmutexをSCOPED_LOCKしたい人は、ネストした関数の関数名と自動開放用変数名の衝突を防ぐために、流行の?プリプロセッサにがんばってもらう方式で下記のようにすればOKなんじゃないかと。ちゃんとロックしたのと逆順で開放されるようです。マニュアルには順序が明記されていないので、ちょっと不安*1ですけど。

// 次の2つの定番(?)マクロをどこかに置いておく
#define CONCAT2(x,y) x##y
#define CONCAT(x,y)  CONCAT2(x,y)

#define SCOPED_LOCK(m)                                                  \
  pthread_mutex_lock(&m);                                               \
  void CONCAT(u_, __LINE__) (pthread_mutex_t** m_)                      \
    { pthread_mutex_unlock(*m_); }                                      \
  __attribute__((__cleanup__( CONCAT(u_, __LINE__) )))                  \
    pthread_mutex_t* CONCAT(scoped_lock_, __LINE__) = &m

例によって日本語の解説がほぼ0の状況っぽいので、小ネタですがメモしておきました。こういう手抜きは大好きです*2

(おまけ) GCCのマニュアルの該当ページ


今回の "cleanup" は、GCCのマニュアルの "Variable Attributes" のところに載ってます。これを含め、マニュアル中の __attribute__ が紹介されている部分をピックアップしてみました。漏れがあったらごめんなさい。

次はC++用のattributeである、__attribute__((init_priority)) をネタにしたいところです。

*1:-fstack-protector でスタックレイアウトをidealにしたときにもちゃんと意図した順序でcleanupされるんだろうか??とか。試せ and/or ソース嫁ですね。

*2:え、 try..catch..finally できるほうが便利ですかそうですか..