C言語による動的配列スタックの実装と基本操作

スタックはLIFO(Last In, First Out)のデータ構造であり、主な操作として初期化、解放、空判定、プッシュ(要素追加)、ポップ(要素削除)、サイズ取得、トップ要素参照が存在する。本実装では、C言語を用いて動的メモリ確保に基づくスタックを3つのファイルで構成し、安全性と拡張性を重視した設計を行う。

ヘッダーファイル:Stack.h

このファイルでは、スタックの抽象化インターフェースを定義する。構造体Stackは内部配列へのポインタ、現在の要素数(size)、および最大容量(capacity)を保持し、typedefにより型名を簡略化している。必要な標準ライブラリ(<stdio.h>, <stdlib.h>, <assert.h>, <stdbool.h>)をインクルードし、各操作関数のプロトタイプを宣言する。

#pragma once

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

typedef int ElemType;

typedef struct {
    ElemType* buffer;
    size_t size;
    size_t capacity;
} Stack;

void stack_init(Stack* s);
void stack_destroy(Stack* s);

void stack_push(Stack* s, ElemType value);
void stack_pop(Stack* s);
size_t stack_length(const Stack* s);
bool stack_is_empty(const Stack* s);
ElemType stack_peek(const Stack* s);

実装ファイル:Stack.c

初期化:stack_init

構造体ポインタの妥当性を確認後、初期容量(4要素)分のヒープ領域をmallocで確保。失敗時はperrorでエラー出力し、sizecapacityを適切に設定する。ここでsizeは「現在の要素数」を表すため、初期値は0とする。

void stack_init(Stack* s) {
    assert(s != NULL);
    s->buffer = (ElemType*)malloc(4 * sizeof(ElemType));
    if (s->buffer == NULL) {
        perror("Failed to allocate memory for stack");
        exit(EXIT_FAILURE);
    }
    s->size = 0;
    s->capacity = 4;
}

解放:stack_destroy

確保済みのバッファ領域をfreeで解放し、ポインタをNULLに初期化。構造体メンバも明示的にゼロクリアすることで、二重解放防止とデバッグ支援を図る。

void stack_destroy(Stack* s) {
    assert(s != NULL);
    free(s->buffer);
    s->buffer = NULL;
    s->size = 0;
    s->capacity = 0;
}

プッシュ:stack_push

要素追加前に容量チェックを行い、不足時はreallocで2倍に拡張。再割り当て失敗時はプログラム終了ではなく安全なエラー処理を実施。成功後、buffer[size]に値を代入し、sizeをインクリメント。

void stack_push(Stack* s, ElemType value) {
    assert(s != NULL);
    if (s->size == s->capacity) {
        size_t new_cap = s->capacity * 2;
        ElemType* new_buf = (ElemType*)realloc(s->buffer, new_cap * sizeof(ElemType));
        if (new_buf == NULL) {
            perror("Failed to reallocate stack memory");
            exit(EXIT_FAILURE);
        }
        s->buffer = new_buf;
        s->capacity = new_cap;
    }
    s->buffer[s->size] = value;
    s->size++;
}

ポップ:stack_pop

空スタックに対するポップを防ぐため、事前にstack_is_emptyで検証。有効な場合のみsizeをデクリメント。要素の実際の削除は不要(論理削除)であるが、必要に応じてbuffer[size]をマスク可能。

void stack_pop(Stack* s) {
    assert(s != NULL);
    assert(!stack_is_empty(s));
    s->size--;
}

サイズ取得:stack_length

sizeフィールドは常に有効要素数を保持するため、単純にその値を返す。

size_t stack_length(const Stack* s) {
    assert(s != NULL);
    return s->size;
}

空判定:stack_is_empty

size == 0を直接評価してブーリアン値を返す。安全のため、ポインタの非NULLチェックを含む。

bool stack_is_empty(const Stack* s) {
    assert(s != NULL);
    return s->size == 0;
}

トップ参照:stack_peek

スタックが空でないことを保証した上で、buffer[size - 1]を読み取り、値を返す。副作用のない参照操作である。

ElemType stack_peek(const Stack* s) {
    assert(s != NULL);
    assert(!stack_is_empty(s));
    return s->buffer[s->size - 1];
}

テストファイル:main.c

実行時の検証には、典型的なシナリオ(例:5要素プッシュ→2回ポップ→サイズとトップ確認)を含む簡潔なmain関数を用いる。各関数呼び出し後に状態をprintfで出力し、動作の整合性を視覚的に確認できる。

タグ: C stack data-structures dynamic-memory-allocation

7月31日 06:44 投稿