第4章: 制御構造

4.1 構造化プログラミングの理論

Böhm-Jacopiniの定理

1966年、Corrado BöhmとGiuseppe Jacopiniは、重要な定理を証明した:

> 任意のアルゴリズムは、順次選択反復の3つの基本構造のみで表現できる。

この定理は構造化プログラミングの理論的基盤となった。

3つの基本構造:

1. 順次(Sequence):
   文1 → 文2 → 文3

2. 選択(Selection):
       ┌── true ──→ 文A
   条件
       └── false ─→ 文B

3. 反復(Iteration):
   ┌──────────────────┐
   ↓                  │
   条件 ── true ──→ 文 ─┘
   │
   false
   ↓

Dijkstraとgoto文

1968年、Edsger Dijkstraは"Go To Statement Considered Harmful"を発表し、goto文の使用を批判した。

Dijkstraの主張:

  • goto文はプログラムの制御フローを複雑にする
  • 構造化された制御構造を使えば、プログラムは証明可能になる
  • コードの可読性と保守性が向上する
  • C言語はgoto文を持つが、現代では限定的な使用に留まる。

    4.2 選択文

    if文

    if (expression)
        statement
    
    if (expression)
        statement1
    else
        statement2
    
    if (expression1)
        statement1
    else if (expression2)
        statement2
    else
        statement3
    

    ダングリングelse問題:

    /* 曖昧なコード */
    if (a)
        if (b)
            x = 1;
    else
        x = 2;
    
    /* C言語の解釈: elseは最も近いifに結合 */
    if (a) {
        if (b)
            x = 1;
        else
            x = 2;
    }
    
    /* 意図が異なる場合は明示的にブレース */
    if (a) {
        if (b)
            x = 1;
    }
    else {
        x = 2;
    }
    

    switch文

    switch (expression) {
        case constant1:
            statements;
            break;
        case constant2:
            statements;
            break;
        default:
            statements;
            break;
    }
    

    フォールスルー:

    /* 意図的なフォールスルー */
    switch (c) {
        case 'a':
        case 'e':
        case 'i':
        case 'o':
        case 'u':
            printf("vowel\n");
            break;
        default:
            printf("consonant\n");
            break;
    }
    
    /* Duffのデバイス(最適化テクニック) */
    void copy(char *to, char *from, int count)
    {
        int n = (count + 7) / 8;
        switch (count % 8) {
            case 0: do { *to++ = *from++;
            case 7:      *to++ = *from++;
            case 6:      *to++ = *from++;
            case 5:      *to++ = *from++;
            case 4:      *to++ = *from++;
            case 3:      *to++ = *from++;
            case 2:      *to++ = *from++;
            case 1:      *to++ = *from++;
                    } while (--n > 0);
        }
    }
    

    C23のattribute:

    switch (x) {
        case 1:
            do_something();
            [[fallthrough]];  /* 意図的なフォールスルーを明示 */
        case 2:
            do_more();
            break;
    }
    

    4.3 反復文

    while文

    while (expression)
        statement
    

    /* 基本的な使用 */
    int i = 0;
    while (i < 10) {
        printf("%d\n", i);
        i++;
    }
    
    /* 無限ループ */
    while (1) {
        /* 永久に実行 */
    }
    
    /* イディオム: ファイル読み込み */
    int c;
    while ((c = getchar()) != EOF) {
        putchar(c);
    }
    

    do-while文

    do
        statement
    while (expression);
    

    /* 最低1回実行 */
    int input;
    do {
        printf("Enter positive number: ");
        scanf("%d", &input);
    } while (input <= 0);
    
    /* イディオム: 複数行マクロ */
    #define SAFE_FREE(p) do { free(p); (p) = NULL; } while (0)
    

    for文

    for (init; condition; update)
        statement
    

    /* 基本 */
    for (int i = 0; i < 10; i++) {
        printf("%d\n", i);
    }
    
    /* 複数の変数 */
    for (int i = 0, j = 10; i < j; i++, j--) {
        printf("i=%d, j=%d\n", i, j);
    }
    
    /* 無限ループ */
    for (;;) {
        /* 永久に実行 */
    }
    
    /* イディオム: ポインタを使った配列走査 */
    for (int *p = arr; p < arr + n; p++) {
        printf("%d\n", *p);
    }
    
    /* イディオム: 連結リストの走査 */
    for (node *n = head; n != NULL; n = n->next) {
        process(n);
    }
    

    4.4 ジャンプ文

    break文

    /* ループを抜ける */
    for (int i = 0; i < 100; i++) {
        if (arr[i] == target) {
            found = i;
            break;  /* ループを終了 */
        }
    }
    
    /* switchを抜ける */
    switch (x) {
        case 1:
            action();
            break;  /* switchを終了 */
    }
    

    continue文

    /* 次の反復へスキップ */
    for (int i = 0; i < 100; i++) {
        if (arr[i] < 0)
            continue;  /* 負の数をスキップ */
        process(arr[i]);
    }
    

    goto文

    /* 基本構文 */
    goto label;
    /* ... */
    label:
        statement;
    

    goto文の適切な使用場面:

    /* 1. ネストしたループからの脱出 */
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (matrix[i][j] == target) {
                found_i = i;
                found_j = j;
                goto found;
            }
        }
    }
    found:
        printf("Found at (%d, %d)\n", found_i, found_j);
    
    /* 2. エラー処理とクリーンアップ */
    int process_file(const char *filename)
    {
        FILE *fp = NULL;
        char *buffer = NULL;
        int result = -1;
    
        fp = fopen(filename, "r");
        if (!fp)
            goto cleanup;
    
        buffer = malloc(BUFFER_SIZE);
        if (!buffer)
            goto cleanup;
    
        /* 処理 */
        result = 0;
    
    cleanup:
        free(buffer);
        if (fp)
            fclose(fp);
        return result;
    }
    

    gotoを避けるべき場面:

    /* 悪い例: gotoによるスパゲッティコード */
        if (cond1) goto label1;
        /* ... */
    label2:
        /* ... */
        goto label3;
    label1:
        /* ... */
        goto label2;
    label3:
        /* ... */
    

    return文

    /* 値を返す */
    int add(int a, int b)
    {
        return a + b;
    }
    
    /* void関数での早期リターン */
    void process(int *p)
    {
        if (!p)
            return;  /* 早期リターン */
        /* 処理 */
    }
    

    4.5 制御構造のパターン

    番兵(Sentinel)

    /* 番兵を使った配列検索 */
    int find_sentinel(int arr[], int n, int target)
    {
        int last = arr[n];
        arr[n] = target;  /* 番兵を設置 */
    
        int i = 0;
        while (arr[i] != target)
            i++;
    
        arr[n] = last;    /* 元に戻す */
        return (i < n) ? i : -1;
    }
    

    ガード節(Guard Clause)

    /* ガード節で早期リターン */
    int divide(int a, int b)
    {
        if (b == 0)
            return -1;  /* ガード */
        if (a == 0)
            return 0;   /* ガード */
        return a / b;
    }
    
    /* vs ネストした条件 */
    int divide_nested(int a, int b)
    {
        int result;
        if (b != 0) {
            if (a != 0) {
                result = a / b;
            } else {
                result = 0;
            }
        } else {
            result = -1;
        }
        return result;
    }
    

    状態機械(State Machine)

    typedef enum {
        STATE_IDLE,
        STATE_RUNNING,
        STATE_PAUSED,
        STATE_STOPPED
    } state_t;
    
    void state_machine(event_t event)
    {
        static state_t state = STATE_IDLE;
    
        switch (state) {
            case STATE_IDLE:
                if (event == EVENT_START) {
                    state = STATE_RUNNING;
                    start_process();
                }
                break;
    
            case STATE_RUNNING:
                if (event == EVENT_PAUSE) {
                    state = STATE_PAUSED;
                    pause_process();
                } else if (event == EVENT_STOP) {
                    state = STATE_STOPPED;
                    stop_process();
                }
                break;
    
            case STATE_PAUSED:
                if (event == EVENT_RESUME) {
                    state = STATE_RUNNING;
                    resume_process();
                } else if (event == EVENT_STOP) {
                    state = STATE_STOPPED;
                    stop_process();
                }
                break;
    
            case STATE_STOPPED:
                /* 終了状態 */
                break;
        }
    }
    

    4.6 複合文とブロック

    ブロックスコープ

    {
        int x = 10;  /* このブロック内でのみ有効 */
        /* ... */
    }
    /* xはここでは見えない */
    
    /* ブロック内での変数シャドウイング */
    int x = 1;
    {
        int x = 2;  /* 外側のxを隠す */
        printf("%d\n", x);  /* 2 */
    }
    printf("%d\n", x);  /* 1 */
    

    複数の宣言を含むfor(C99)

    for (int i = 0; i < n; i++) {
        /* iはこのループ内でのみ有効 */
    }
    /* iはここでは見えない */
    

    4.7 条件式のイディオム

    真偽値の扱い

    /* 冗長なコード */
    if (condition == true) { }
    if (condition != false) { }
    
    /* 簡潔なコード */
    if (condition) { }
    if (!condition) { }
    
    /* ポインタのNULLチェック */
    if (ptr != NULL) { }
    if (ptr) { }  /* より簡潔 */
    
    if (ptr == NULL) { }
    if (!ptr) { }  /* より簡潔 */
    

    短絡評価の利用

    /* NULLチェックと参照を1行で */
    if (ptr && ptr->value > 0) { }
    
    /* デフォルト値の設定 */
    int value = get_value() || default_value;
    
    /* エラーチェックの連鎖 */
    if (init() && configure() && start()) {
        /* 全て成功 */
    }
    

    assert

    #include <assert.h>
    
    void process(int *arr, int n)
    {
        assert(arr != NULL);
        assert(n > 0);
        /* ... */
    }
    
    /* リリースビルドでは無効化 */
    /* gcc -DNDEBUG ... */
    

    4.8 エラー処理パターン

    戻り値によるエラー処理

    /* エラーコードを返す */
    int read_file(const char *filename, char **content)
    {
        FILE *fp = fopen(filename, "r");
        if (!fp)
            return -1;
    
        /* 処理 */
    
        fclose(fp);
        return 0;
    }
    
    /* 呼び出し側 */
    if (read_file("test.txt", &content) != 0) {
        /* エラー処理 */
    }
    

    errnoによるエラー処理

    #include <errno.h>
    #include <string.h>
    
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        fprintf(stderr, "Error: %s\n", strerror(errno));
        return -1;
    }
    

    gotoによるクリーンアップ

    int complex_operation(void)
    {
        int ret = -1;
        int *arr1 = NULL, *arr2 = NULL;
        FILE *fp = NULL;
    
        arr1 = malloc(sizeof(int) * 100);
        if (!arr1)
            goto error;
    
        arr2 = malloc(sizeof(int) * 200);
        if (!arr2)
            goto error;
    
        fp = fopen("data.txt", "r");
        if (!fp)
            goto error;
    
        /* 処理 */
        ret = 0;
    
    error:
        free(arr1);
        free(arr2);
        if (fp)
            fclose(fp);
        return ret;
    }
    

    4.9 まとめ

    本章では、C言語の制御構造について学んだ:

  • 構造化プログラミング: Böhm-Jacopiniの定理
  • 選択文: if, switch
  • 反復文: while, do-while, for
  • ジャンプ文: break, continue, goto, return
  • パターン: 番兵、ガード節、状態機械
  • スコープ: ブロックスコープ
  • エラー処理: 戻り値、errno、クリーンアップ
  • 次章では、関数について学ぶ。

    ---

    参考文献

  • Böhm, C., & Jacopini, G. (1966). "Flow Diagrams, Turing Machines and Languages with Only Two Formation Rules", Communications of the ACM, 9(5)
  • Dijkstra, E. W. (1968). "Go To Statement Considered Harmful", Communications of the ACM, 11(3)
  • Kernighan, B. W., & Plauger, P. J. (1978). "The Elements of Programming Style", 2nd Edition, McGraw-Hill
  • ISO/IEC 9899:2011, Programming languages — C