ALGOBIT

2010/10/04

ハノイとバベルの塔 第三階 – C におけるモジュールの分離

Filed under: 離散的な気まぐれ — タグ: , , , — Kohyama @ 13:32

C言語で, 第一階 の Haskell の記述と同様な,
データ生成部分と出力部分を分離する方法について考えます.
いろいろなやり方があると思いますが, 以下に二例ほど記述したいと思います.

手順を出力する関数を用意し, パズルを解く関数への引数として, 用意しておいた関数への参照を渡すことができます.

#include <stdio.h>
#include <stdlib.h>

static void
printop(int i, char src, char dst)
{
    printf("move disc #%d from %c to %c\n", i, src, dst);
}

static void
hanoi(char src, char tmp, char dst, int n, void (*pp)(int, char, char))
{
    if (n == 0)
        return;
    hanoi(src, dst, tmp, n - 1, pp);
    pp(n, src, dst);
    hanoi(tmp, src, dst, n - 1, pp);
}

int
main(int argc, char *argv[])
{
    int n = (argc < 2)?3:atoi(argv[1]);
    hanoi('A', 'B', 'C', n, &printop);
}

グラフィカルなプログラムではユーザからのアクション(イベント)を検知する部分がライブラリとして用意されており, ライブラリ関数にそのイベントの処理方法を渡すような場合に良く使われる方法で「コールバック関数」または「デリゲート」などと呼ばる方法です.

もうひとつの例は, パズルを解く部分とデータを表示する部分で異なるプロセスもしくはスレッドに分かれて片方から片方へデータを引き渡す方法です.
以下の実装では UNIX系の OS でプロセスをフォークして, データをパイプ渡しにします.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

struct _op {
    int i;
    char dst;
    char src;
};

static void
printop()
{
    struct _op op;

    while (read(0, &op, sizeof(op)) > 0)
        printf("move disc #%d from %c to %c\n", op.i, op.src, op.dst);
}

static void
hanoi(char src, char tmp, char dst, int n)
{
    struct _op op = {n, src, dst};

    if (n == 0)
        return;
    hanoi(src, dst, tmp, n - 1);
    write(1, &op, sizeof(op));
    hanoi(tmp, src, dst, n - 1);
}

int
main(int argc, char *argv[])
{
    int n = (argc < 2)?3:atoi(argv[1]);
    int fildes[2];
    pid_t pid;

    if (pipe(fildes) < 0) {
        perror("pipe");
        exit(1);
    }
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }
    if (pid == 0) {
        close(fildes[0]);
        dup2(fildes[1], 1);
        close(fildes[1]);
        hanoi('A', 'B', 'C', n);
        close(1);
    } else {
        close(fildes[1]);
        dup2(fildes[0], 0);
        close(fildes[0]);
        printop();
        close(0);
        _exit(0);
    }
    wait(0);
    return 0;
}

引き渡すデータが struct _op 型であるという約束の元, hanoi 関数は, 手順を標準出力に次々と出力し, printop 関数は標準入力から手順が入力されてくる限りそれを読み取って出力します. hanoi 関数の実装者は, 処理したデータがどう扱われるかについては関知しません.
もちろん, 他のプラットフォームでも本質的に同様な実装が可能です.

このコード規模では分離のための記述が全体に占める割合が大きくなってしまいますが, もう少し大きなプログラムであれば, 実用されている方式と思います.


デビッド・A. クリ
アスキー
発売日:1991-07

実際に役に立つプログラムは直接なり間接なり OS と対話しなければならない.
とくに UNIX オペレーティングシステムを記述するために生まれた C 言語は, より上位のプログラミング環境の下位で OS と直接対話する基盤である.
システムコールプログラミングへようこそ.
Steve Oualline
オライリー・ジャパン
発売日:1998-06-15


Copyright © 2010 Yoshinori Kohyama All Rights Reserved.