こるーちん☆いん☆しー
id:kikx さんに面白いソースコードを教えてもらいました。
http://www.google.com/codesearch/p?hl=ja#ZmjNDPVeNGc/ssh.c&q=putty%20ssh.c&sa=N&cd=1&ct=rc&l=377
C のマクロでコルーチンを実現しています。
マクロだけ見て理解するのはちょっと大変ですが、展開後のソースコードを見れば何が行われているかは一目瞭然だと思います。
#define crBegin(v) { int *crLine = &v; switch(v) { case 0:; #define crFinish(z) } *crLine = 0; return (z); } #define crReturn(z) \ do {\ *crLine =__LINE__; return (z); case __LINE__:;\ } while (0) int gen(){ static int context = 0; crBegin(context); crReturn(1); crReturn(2); crReturn(3); crReturn(4); crFinish(5); } int main(){ int i, a = 0; for (i = 0; i < 100; i++) a += gen(); return 0; }
これが
int gen(){ static int context = 0; { int *crLine = &context; switch(context) { case 0:;; do { *crLine =11; return (1); case 11:; } while (0); do { *crLine =12; return (2); case 12:; } while (0); do { *crLine =13; return (3); case 13:; } while (0); do { *crLine =14; return (4); case 14:; } while (0); } *crLine = 0; return (5); }; } int main(){ int i, a = 0; for (i = 0; i < 100; i++) a += gen(); return 0; }
こうなる。
位置情報としてのコンテキストを int で保存しておいて、case でジャンプしているんですねえ。
実際にどういう風に使われてるか見てみましょう。まあたったの 8 箇所でしか使われてないようなんですが…
static int do_ssh_init(Ssh ssh, unsigned char c) { struct do_ssh_init_state { int vslen; char version[10]; char *vstring; int vstrsize; int i; int proto1, proto2; }; crState(do_ssh_init_state); crBegin(ssh->do_ssh_init_crstate); /* Search for a line beginning with the string "SSH-" in the input. */ for (;;) { if (c != 'S') goto no; crReturn(1); if (c != 'S') goto no; crReturn(1); if (c != 'H') goto no; crReturn(1); if (c != '-') goto no; break; no: while (c != '\012') crReturn(1); crReturn(1); } ...
これはあまり面白くない例ですが…コードの動作としてはコメントの通りですね。
crState は「位置以外」のコンテキスト、つまりは本来ならスタックに積むべきローカル変数を保持するために利用されます。
コルーチンは、状態遷移を値としてではなく、コードとして自然に表すことができるので、一部の処理を書く際には非常に有用なのですが、これを C のマクロを駆使して実現するというのは…いい話だなー。
putty 的には、
[recv やら read 相当のことをしている何か]
- ssh_receive
- ssh_got_data
- do_ssh_init
みたいになっていて、ようはバッファを少しずつ処理していくんだけれど、状態遷移大変だよ!→コールチンの出番、という感じらしいです。
気持ちは分かるが 8 箇所ぐらいしかないなら頑張ろうよ…いや 8 箇所しかないからこそマクロでごまかしたともいえるのかなあ。
ちなみに、真っ当な C のコルーチンの実装としては libtask なんかがあるようです。ucontext で真面目にやってる系ですね。環境ごとに微妙に違う ucontext の差を吸収したりしつつコルーチンを実装しているらしいです。大変だなあ。
あとは C++ だと boost::coroutine とか id:y-hamigaki さんのとかがありますね。こっちは確かアセンブリで頑張ったりとかでもっと大変だったはずで、大変だなあ。