Namaran

Code daily. Without assist.

2026-08-23

memo_square.c

#include <stdio.h>

typedef int (*compute_fn)(int);

static int calls = 0;

int square(int n) {
    calls++;
    return n * n;
}

typedef struct {
    compute_fn compute;
    int cache[16];
    unsigned char cached[16];
} memo_t;

int memo_get(memo_t *m, int n) {
    if (!m->cached[n]) {
        m->cache[n] = m->compute(n);
        m->cached[n] = 1;
    }
    return m->cache[n];
}

int main(void) {
    memo_t m = { .compute = square };
    printf("%d\n", memo_get(&m, 4));
    printf("%d\n", memo_get(&m, 4));
    printf("%d\n", memo_get(&m, 5));
    printf("calls=%d\n", calls);
}

m->computeは関数ポインタで、実際にどの関数を呼ぶかは実行時にしか決まりません(動的ディスパッチ)。これは何を出力するでしょうか?

Answer

161625calls=2を1行ずつ。memo_getnごとにcached[n]を見て、まだ計算していなければm->compute(n)で計算し結果をキャッシュします。m->computesquareという具体的な関数を直接名指しで呼んでいるのではなく、構造体に保持された関数ポインタ経由で呼んでいます。どの関数が実行されるかはコンパイル時の呼び出し箇所では決まらず、m.compute = squareという実行時の代入によって決まる、というのが動的ディスパッチです(対して、コードのどこかにsquare(n)と直接書けば、呼ぶ関数はコンパイル時に確定する静的ディスパッチになります)。memo_get(&m, 4)は2回呼ばれますが、1回目でcached[4]が立つので2回目はcomputeを呼ばずキャッシュの16を返します。memo_get(&m, 5)は初めてのnなのでcomputeを呼び25を得ます。結果としてsquareが実際に呼ばれる(callsが増える)のはn=4n=5のときの2回だけです。