2012年12月22日土曜日

言語C でスコープドポインタを実現する(その4)

リファレンスの生成

リファレンスの生成は次のように行っている。
Reference NO_INSTRUMENT new_Reference(void* object,
 void (*destructor)(void* object))
{
 Reference this = (Reference)malloc(sizeof(struct Reference));
 if (this != null) {
  this->object = object;
  this->frames = frames;
  this->referenceCounter = 1;
  this->destructor = destructor;
 }
 add(this);
 return this;
}
ヒープ領域からメモリを割り当て、オブジェクトとデストラクタ、framesを記憶し、参照カウンタは1で初期化する。
この実験コードでの framesはグローバル変数であり、複数スレッドに対応していないが、マルチスレッド化するときには framesは TLSに配置される予定である。その場合、Referenceがどの framesに対応しているか素早く探すためのものである。
TLSからキーで検索するならこれは不要である。

つぎに生成したリファレンスを framesに登録するために add関数を呼ぶ。
メモリ割り当てに失敗したときにも addししまうのは暫定のためである。
再帰呼び出しされる関数の中でリファレンスが作られるなら、単純に if文の中に入れれば良いと言うわけにはいかない(復帰時に他の階層のリファレンスを破棄する恐れがある)ので、何らかの配慮が必要になる。

その add関数はつぎの通り。
private void add(Reference r) {
 int top = ArrayList_size(frames) - 1;
 Frame frame = ArrayList_get(frames, top);
 ArrayList_add(frame->references, r);
}

framesリストの先頭にある frameリストを取り出す。
frameリストにリファレンスを追加する。

framesと frameの各リストの構造を(その2)の例題 testAutoCollectを例に図式化すると次のようになる。





洗濯機が貰えるなら僕らもノーベル賞取ろうよ。

言語C でスコープドポインタを実現する(その3)

関数から復帰するとき

前回の使用例のように関数から復帰するときの処理について紹介する。
gccのフック機能を利用してつぎの処理を行う。
void NO_INSTRUMENT __cyg_profile_func_exit(void* functionAddress,
 void* call_site)
{
 if (!suppressed) {
  suppressed = true;
  popFrame(functionAddress);
  suppressed = false;
 }
}
関数呼び出し時同様、自身のトレースを行わないようなフラグ処理を行う。
(ただし、マルチスレッド未対応)
関数呼び出しに対応したフレームをポップする。

popFrameはこんな感じ。
private void popFrame(void* functionAddress) {
 boolean done = false;

 while (!done && ArrayList_size(frames) > 0) {
  int top = ArrayList_size(frames) - 1;
  Frame frame = (Frame)ArrayList_removeAt(frames, top);
  done = (frame->functionAddress == functionAddress);
  Frame_finalize(frame);
 }
}

framesリストの新しいものから順に現在の関数名が現れるまで、古いフレームを捨ててゆく。
こうすると、途中に -finstrument-functionsを指定せずにコンパイルされた関数があってもポップが正しくできる。
また、最低1つはポップ済みのため、関数を再帰的に呼び出しても処理できる。
再起呼び出しの中で以前紹介した例外機構や longjmpによる脱出にも対応可能である。

ただし、落とし穴もある。
-finstrument-functionsを使っていない関数に復帰しただけではすぐに解放処理が行われない。
再起呼び出しにより同じ関数を通ったとしても、条件分岐により catchしたりしなかったりした場合に正しくポップされるかは未だ考慮されていない。
関数アドレスではなく、現在のスタックフレームを比較する必要があるかも知れない。

2012年10月5日金曜日

言語C でスコープドポインタを実現する(その2)

新しい参照の作成

ローカル変数として参照を作成するときはつぎのようにする。
 lwd_Reference r = new_lwd_Reference(new_C("3345g"),
  (void (*)(void* object))C_finalize);
参照を生成するとき、第1引数としてオブジェクトのポインタを、第2引数にそのオブジェクトのデストラクタを指定する。

構造体やクラスのメンバが参照の場合も同様に初期化できる。

関数から復帰時に自動的に解放されるオブジェクト

スコープドポインタとしての動作を確認する例を示す。
テストに使用する簡単なクラス Cはつぎのようなもの。
/****************************************************************
 test target C
****************************************************************/

static boolean cFinalized;

typedef struct C* C;
struct C {
 char* string;
};

C new_C(char* string) {
 C this = (C)malloc(sizeof(struct C));
 if (this != null) {
  cFinalized = false;
  this->string = strdup(string);
 }
 printf("C object created.\n");fflush(stdout);
 return this;
}

void C_finalize(C this) {
 if (this != null) {
  printf("C object finalized. %s\n", this->string);fflush(stdout);
  if (this->string != null)
   free(this->string);
  free(this);
  cFinalized = true;
 }
}
コンストラクタとデストラクタがあるだけだが、デストラクタが実行されると外部変数の cFinalizedを trueにセットする。

テストケースはつぎの通り。
static void function0() {
 reference r = new_reference(new_C("3345g"),
  (void (*)(void* object))C_finalize);

 Assert_false("まだ削除されていないはず。", cFinalized);
}

void testAutoCollect(TestCase testCase) {
 Assert_false("まだ削除されていないはず。", cFinalized);
 function0();
 Assert_true("削除されているはず。", cFinalized);
}
testAutoCollect関数から呼ばれた function0関数の中で、参照 rを生成する。
関数復帰直前に cFinalizeは未だ falseであるが、testAutoCollect関数に戻ってきた後は cFinalizedが trueになる。

実行すると、標準出力にはつぎが出力されている。
[CUnit] ./ReferenceTest: testAutoCollect
C object created.
C object finalized. 3345g
デストラクタを明示的に呼んではいないが、トレースログにはデストラクタが呼ばれている様子がわかる。

2万円以上のカードをパスケースに入れて改札機にタッチしますと他のカードが処理されることがあります。 -- それは不思議な現象だ

2012年9月30日日曜日

言語C でスコープドポインタを実現する(その1)

gccの拡張機能に頼ることになるが、finstrument-functions機能を使用すると、スコープドポインタもどきが作れる。
これは、関数が呼び出されたときと復帰するときに、2つの特殊な関数
  • void __cyg_profile_func_enter(void *this_fn, void *call_site);
  • void __cyg_profile_func_exit(void *this_fn, void *call_site);
にフックする関数トレース機能を利用する。

原理

任意のブロックというわけにはいかないが、関数内で作られたスコープドポインタを関数から復帰するときに削除する。
スコープドポインタには予め参照先のオブジェクトのデストラクタを設定しておき、スコープドポインタを削除するに先立って参照先のデストラクタを呼び出す。
なお、このプロジェクトでは scoped_ptrという名前ではなく、lwdパッケージの Referenceクラスを用い、「参照」と呼ぶことにする。
その実、この「参照」クラスはスコープドポインタだけではなくシェアードポインタの機能も持たせてある。
例によってこれは実験段階で、幾つかのケースでしか動作が確認できていない。

関数が呼ばれたとき

__cyg_profile_func_enterの中で、つぎのようなことを行う。
関数内で作成された参照を記憶するリストを作成する。
このリストは呼び出しごとに作成されるため、関数を再帰的に呼び出してもどの呼び出しレベルで作られたものか区別することができる。
private boolean suppressed;

private ArrayList frames;

typedef struct Frame* Frame;

struct Frame {
        void* functionAddress;
        ArrayList references;
};

suppressedは、Referenceクラス自身をトレースして循環に陥らないようにするためのフラグ。
フックの処理を開始するときにセットし、抜けるときにリセットする。
このフラグがセットされているときに再びフック関数が呼ばれたときは Referenceの処理中ということで、フック内の処理を全てスキップする。
予め、つぎを定義しておき、
#define NO_INSTRUMENT __attribute__((no_instrument_function))
Referenceクラスの内部の関数には NO_INSTRUMENTを指定してフック関数が呼ばれないようにしているが、Referenceクラス内部で使用している softiesライブラリの関数がトレース対象となるのを防止している。

framesリストは、関数が呼ばれるたびに Frameオブジェクトを追加し、復帰するたびに削除されるリストである。
マルチスレッドで使えるようにスレッドローカルにすべきだが、現在はそうなっていない。
Frameクラスには、関数のアドレスとそのなかで作成された参照のリストが含まれている。関数内で作成される参照の数は不定のため固定長配列ではなく可変長リストの ArrayListクラスを用いている。
Frameクラスは Referenceクラスの内部クラスとして実装されており、無名パッケージに属している。

それでは実際のフック関数をつくってみる。
void NO_INSTRUMENT __cyg_profile_func_enter(void* functionAddress,
        void* callSite)
{
        if (!suppressed) {
                suppressed = true;
                pushNewFrame(functionAddress);
                suppressed = false;
        }
}

pushNewFrame関数が、関数が呼ばれたときのリスト作成を行っている。
private void pushNewFrame(void* functionAddress) {
        Frame frame = new_Frame(functionAddress);
        ArrayList_add(frames, frame);
}

Frameのコンストラクタはこんな感じ。
private Frame new_Frame(void* functionAddress) {
        Frame this = (Frame)malloc(sizeof(struct Frame));

        if (this != null) {
                this->functionAddress = functionAddress;
                this->references = new_ArrayList();
        }

        return this;
}
これで参照の管理の準備ができた。

その2につづく。

心地良いさ、納戸に伝わる君の I love you.

2012年8月26日日曜日

インポート (import)

今日は、インポートの実現方法について解説する。 と言っても、結構ばかばかしい。


これまでの投稿で示した例題では無名パッケージのクラスだったが、命名規則 (naming convention)で示しているように softies projectではパッケージの概念を導入している。
たとえば、単体テストフレームワーク CUnitに Assertクラスがあるが、もしテスト対象で既に別の Assertという名前を使用していると、シンボルの衝突が発生してしまう。
そのため、CUnitは衝突が起こりにくいよう、lwd_unitパッケージに Assertクラスを置いている。

完全修飾名 (fully qualified name)

インポートを使わない場合の Assertクラスの使用例をつぎに示す。(example of using Assert class without import.)
/* 25.Aug.2012 kei */

#include <lwd/unit/Assert.h>

int main() {
        int x = 0;
        lwd_unit_Assert_equalsInt("x equals 0.", 0, x);

        return 0;
}
この様に、長い名前で equalsIntを呼び出さなければならない。
シンボルの衝突は置きにくいが、冗長である。

インポート (import)

インポートを使用した場合の Assertクラスの使用例をつぎに示す。(example of using Assert class with import.)
/* 25.Aug.2012 kei */

#define import_lwd_unit_Assert

#include <lwd/unit/Assert.h>

int main() {
        int x = 0;
        Assert_equalsInt("x equals 0.", 0, x);

        return 0;
}
import_lwd_unit_Assertを定義するだけで、パッケージ名を省き、クラス名と関数名だけで関数呼び出しができるようになる。

仕掛け (mechanism)

Assertクラスのヘッダには次のような記述が含まれており、import_lwd_unit_Assertを定義することにより、Assertクラスの関数名などの長い名前の省略形がマクロ定義されるようになっている。
#ifdef import_lwd_unit_Assert
        #define Assert_equalsInt        lwd_unit_Assert_equalsInt
        #define Assert_equalsLongLong   lwd_unit_Assert_equalsLongLong
        #define Assert_equalsDouble     lwd_unit_Assert_equalsDouble
        #define Assert_equalsString     lwd_unit_Assert_equalsString
        #define Assert_equals           lwd_unit_Assert_equals
        #define Assert_null             lwd_unit_Assert_null
        #define Assert_notNull          lwd_unit_Assert_notNull
        #define Assert_false            lwd_unit_Assert_false
        #define Assert_true             lwd_unit_Assert_true
        #define Assert_fail             lwd_unit_Assert_fail
#endif /* import_lwd_unit_Assert */
仕掛けは単純だが、これがあるかどうかで使いやすさが違ってくる。
通常はインポートを使用し、名前が衝突する場合にのみ FQNで記述すればよい。
なお、クラスのメンバが追加されたり名称に変更があった場合など、忘れずに省略形の修正も行わなければならない。

原発を灰色にするのは…