Cで継続渡し
できた。試行錯誤している内に無駄に凝った作りになってしまった。ここまで来ると完全に手段が目的と化しているな。
- fib.h:
#ifndef __FIB_H__ #define __FIB_H__ typedef void (*destructor_t)(); typedef struct object { int ref_count; size_t size; destructor_t destructor; } *object_t; void addRef(object_t obj); void release(object_t obj); object_t newObject(size_t size, destructor_t destructor); void deleteObject(object_t obj); typedef void* (*func_t)(); typedef struct closure { // inherited from object_t int ref_count; size_t size; destructor_t destructor; // own data func_t func; int var_num; void *vars[0]; } *closure_t; closure_t newClosure(); #endif /* __FIB_H__ */
- fib.c:
#include <stdio.h> #include <stdlib.h> #include "fib.h" void addRef(object_t obj) { obj->ref_count++; } void release(object_t obj) { obj->ref_count--; if (obj->ref_count <= 0) { if (obj->destructor) obj->destructor(obj); deleteObject(obj); } } object_t newObject(size_t size, destructor_t destructor) { object_t obj = malloc(size); #ifdef DEBUG fprintf(stderr, "malloc: %p\n", obj); #endif obj->ref_count = 0; obj->size = size; obj->destructor = destructor; addRef(obj); return obj; } void deleteObject(object_t obj) { #ifdef DEBUG size_t size = obj->size; char *p = (char*) obj; // filled by `de ad be ef' ... pattern for (int i = 0; i < size / 4; i++) { *p++ = '\xde'; *p++ = '\xad'; *p++ = '\xbe'; *p++ = '\xef'; } switch (size % 4) { case 3: p[2] = '\xbe'; case 2: p[1] = '\xad'; case 1: p[0] = '\xde'; } #endif /* DEBUG */ free(obj); #ifdef DEBUG fprintf(stderr, "free: %p\n", obj); #endif } closure_t newClosure(int var_num) { size_t size = sizeof(struct closure) + sizeof(void*) * var_num; closure_t c = (closure_t) newObject(size, NULL); c->var_num = var_num; return c; } int fib_cps(int n, closure_t k); static int fib_cps_2(int v2, closure_t k2) { int v1 = (int) k2->vars[1]; closure_t k = k2->vars[0]; release((object_t) k2); return (int) k->func(v1 + v2, k); } static int fib_cps_1(int v1, closure_t k1) { int n = (int) k1->vars[1]; closure_t c = newClosure(3); c->func = (func_t) fib_cps_2; c->vars[0] = k1->vars[0]; c->vars[1] = (void*) v1; c->vars[2] = (void*) n; release((object_t) k1); return (int) fib_cps(n - 2, c); } int fib_cps(int n, closure_t k) { if ((n == 1) || (n == 2)) { return (int) k->func(1, k); } else { closure_t c = newClosure(2); c->func = (func_t) fib_cps_1; c->vars[0] = (void*) k; c->vars[1] = (void*) n; return fib_cps(n - 1, c); } } static int fib_cps_0(int n, closure_t k) { release((object_t) k); return n; } int main(int argc, char *argv[]) { closure_t c = newClosure(0); c->func = (func_t) fib_cps_0; for (int i = 1; i < 10; i++) { addRef((object_t) c); printf("Fibonacci(%d) = %d\n", i, fib_cps(i, c)); } release((object_t) c); return EXIT_SUCCESS; }
見て分かる通り、参照カウンタを使って自前でオブジェクトの寿命を管理している。ガベージコレクションがない言語でクロージャを使うのがいかに大変かということがよく分かった。
なお、C99で拡張された機能を使っているので、gccでコンパイルする場合は
% gcc -Wall -std=c99 -DDEBUG fib.c
のように-std=c99を付けてコンパイルして下さい。