【C言語】配列の要素数を動的に変更できる(可変)ようにする方法

C言語でプログラムを作成する際、配列のサイズを動的に変更できると非常に便利です。

この記事では、動的メモリ割り当ての基本から、配列のサイズを変更する方法、そしてよくある問題とその対策について解説します。

初心者の方でも理解しやすいように、具体的なサンプルコードとともに説明していきますので、ぜひ参考にしてください。

目次から探す

配列の動的メモリ割り当て

C言語では、配列のサイズをコンパイル時に決定する静的配列と、実行時に動的に決定する動的配列があります。

動的配列を使用することで、プログラムの実行中に配列のサイズを変更することが可能になります。

ここでは、動的配列の宣言と初期化、要素へのアクセス方法、そしてメモリリークを防ぐための注意点について解説します。

動的配列の宣言と初期化

動的配列を使用するためには、まずメモリを動的に確保する必要があります。

C言語では、malloc関数calloc関数を使用してメモリを動的に確保します。

以下に、mallocを使用した動的配列の宣言と初期化の例を示します。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *array;
    int size = 5;
    // メモリの動的確保
    array = (int *)malloc(size * sizeof(int));
    // メモリ確保の成功確認
    if (array == NULL) {
        printf("メモリの確保に失敗しました。\n");
        return 1;
    }
    // 配列の初期化
    for (int i = 0; i < size; i++) {
        array[i] = i * 10;
    }
    // 配列の内容を表示
    for (int i = 0; i < size; i++) {
        printf("array[%d] = %d\n", i, array[i]);
    }
    // メモリの解放
    free(array);
    return 0;
}

この例では、malloc関数を使用して整数型の配列を動的に確保し、初期化しています。

malloc関数は、指定したバイト数のメモリを確保し、その先頭アドレスを返します。

確保したメモリは、使用後にfree関数で解放する必要があります。

配列の要素にアクセスする方法

動的配列の要素にアクセスする方法は、静的配列と同じです。

配列のインデックスを使用して要素にアクセスできます。

以下に、動的配列の要素にアクセスする例を示します。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *array;
    int size = 5;
    // メモリの動的確保
    array = (int *)malloc(size * sizeof(int));
    // メモリ確保の成功確認
    if (array == NULL) {
        printf("メモリの確保に失敗しました。\n");
        return 1;
    }
    // 配列の初期化
    for (int i = 0; i < size; i++) {
        array[i] = i * 10;
    }
    // 配列の要素にアクセスして表示
    for (int i = 0; i < size; i++) {
        printf("array[%d] = %d\n", i, array[i]);
    }
    // メモリの解放
    free(array);
    return 0;
}

この例では、array[i]を使用して配列の要素にアクセスし、その値を表示しています。

動的配列でも、静的配列と同様にインデックスを使用して要素にアクセスできます。

メモリリークを防ぐための注意点

動的メモリを使用する際には、メモリリークを防ぐためにいくつかの注意点があります。

メモリリークとは、確保したメモリを解放せずにプログラムが終了することを指します。

これにより、システムのメモリ資源が無駄に消費され、最悪の場合システムが不安定になることがあります。

  1. メモリの解放: 動的に確保したメモリは、使用後に必ずfree関数で解放する必要があります。

これを怠るとメモリリークが発生します。

  1. メモリ確保の成功確認: malloccalloc関数がNULLを返す場合、メモリの確保に失敗しています。

この場合、確保したメモリを使用しないように注意が必要です。

  1. ポインタの再利用: 動的メモリを再利用する場合、古いメモリを解放してから新しいメモリを確保するようにします。

これにより、不要なメモリの消費を防ぐことができます。

以下に、メモリリークを防ぐための例を示します。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *array;
    int size = 5;
    // メモリの動的確保
    array = (int *)malloc(size * sizeof(int));
    // メモリ確保の成功確認
    if (array == NULL) {
        printf("メモリの確保に失敗しました。\n");
        return 1;
    }
    // 配列の初期化
    for (int i = 0; i < size; i++) {
        array[i] = i * 10;
    }
    // 配列の内容を表示
    for (int i = 0; i < size; i++) {
        printf("array[%d] = %d\n", i, array[i]);
    }
    // メモリの解放
    free(array);
    return 0;
}

この例では、動的に確保したメモリを使用後にfree関数で解放しています。

これにより、メモリリークを防ぐことができます。

以上が、配列の動的メモリ割り当てに関する基本的な解説です。

次のセクションでは、配列のサイズ変更について詳しく解説します。

配列のサイズ変更

C言語では、配列のサイズを動的に変更するためにrealloc関数を使用します。

この関数を使うことで、既存のメモリブロックのサイズを変更することができます。

以下では、realloc関数の基本的な使い方と、配列のサイズを増やす場合と減らす場合の具体的な方法について説明します。

realloc関数の使い方

reallocの基本的な使い方

realloc関数は、既存のメモリブロックのサイズを変更するために使用されます。

以下に基本的な使い方を示します。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *array;
    int initial_size = 5;
    int new_size = 10;
    // 初期メモリ割り当て
    array = (int *)malloc(initial_size * sizeof(int));
    if (array == NULL) {
        printf("メモリの割り当てに失敗しました\n");
        return 1;
    }
    // メモリ再割り当て
    array = (int *)realloc(array, new_size * sizeof(int));
    if (array == NULL) {
        printf("メモリの再割り当てに失敗しました\n");
        return 1;
    }
    // 配列の使用
    for (int i = 0; i < new_size; i++) {
        array[i] = i;
        printf("%d ", array[i]);
    }
    // メモリの解放
    free(array);
    return 0;
}

この例では、最初にmalloc関数を使って5つの整数を格納できるメモリを割り当て、その後realloc関数を使って10個の整数を格納できるようにメモリを再割り当てしています。

メモリ再割り当ての成功と失敗の処理

realloc関数は、メモリの再割り当てに成功した場合、新しいメモリブロックのポインタを返しますが、失敗した場合はNULLを返します。

したがって、再割り当てが成功したかどうかを確認することが重要です。

int *temp = (int *)realloc(array, new_size * sizeof(int));
if (temp == NULL) {
    printf("メモリの再割り当てに失敗しました\n");
    free(array); // 元のメモリを解放
    return 1;
}
array = temp;

このように、一時的なポインタを使って再割り当てを行い、成功した場合にのみ元のポインタを更新することで、メモリリークやクラッシュを防ぐことができます。

配列のサイズを増やす

配列のサイズを増やす場合、realloc関数を使って新しいサイズを指定します。

以下に具体例を示します。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *array;
    int initial_size = 5;
    int new_size = 10;
    // 初期メモリ割り当て
    array = (int *)malloc(initial_size * sizeof(int));
    if (array == NULL) {
        printf("メモリの割り当てに失敗しました\n");
        return 1;
    }
    // 初期配列の設定
    for (int i = 0; i < initial_size; i++) {
        array[i] = i;
    }
    // メモリ再割り当て
    int *temp = (int *)realloc(array, new_size * sizeof(int));
    if (temp == NULL) {
        printf("メモリの再割り当てに失敗しました\n");
        free(array);
        return 1;
    }
    array = temp;
    // 新しい要素の初期化
    for (int i = initial_size; i < new_size; i++) {
        array[i] = i;
    }
    // 配列の使用
    for (int i = 0; i < new_size; i++) {
        printf("%d ", array[i]);
    }
    // メモリの解放
    free(array);
    return 0;
}

この例では、最初に5つの要素を持つ配列を作成し、その後10個の要素を持つようにサイズを増やしています。

新しい要素には初期値を設定しています。

配列のサイズを減らす

配列のサイズを減らす場合も、realloc関数を使って新しいサイズを指定します。

以下に具体例を示します。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *array;
    int initial_size = 10;
    int new_size = 5;
    // 初期メモリ割り当て
    array = (int *)malloc(initial_size * sizeof(int));
    if (array == NULL) {
        printf("メモリの割り当てに失敗しました\n");
        return 1;
    }
    // 初期配列の設定
    for (int i = 0; i < initial_size; i++) {
        array[i] = i;
    }
    // メモリ再割り当て
    int *temp = (int *)realloc(array, new_size * sizeof(int));
    if (temp == NULL) {
        printf("メモリの再割り当てに失敗しました\n");
        free(array);
        return 1;
    }
    array = temp;
    // 配列の使用
    for (int i = 0; i < new_size; i++) {
        printf("%d ", array[i]);
    }
    // メモリの解放
    free(array);
    return 0;
}

この例では、最初に10個の要素を持つ配列を作成し、その後5個の要素を持つようにサイズを減らしています。

サイズを減らす場合、不要な要素は自動的に削除されます。

以上が、realloc関数を使った配列のサイズ変更の基本的な方法です。

これらの方法を使うことで、動的に配列のサイズを変更し、効率的なメモリ管理を行うことができます。

よくある問題とその対策

動的メモリ割り当てを使用する際には、いくつかのよくある問題に注意する必要があります。

ここでは、メモリリーク、reallocの失敗時の対処法、ポインタの誤使用によるバグの防止について解説します。

メモリリークの原因と対策

メモリリークとは、動的に割り当てたメモリが解放されずにプログラムが終了することを指します。

これにより、メモリが無駄に消費され、最悪の場合システムのメモリが枯渇することがあります。

メモリリークの原因

  1. メモリの解放忘れ: mallocreallocで割り当てたメモリをfreeしない。
  2. ポインタの上書き: 動的に割り当てたメモリのアドレスを保持しているポインタを上書きしてしまう。

メモリリークの対策

  1. メモリの解放を忘れない: 必ずfree関数を使ってメモリを解放する。
  2. ポインタの管理: ポインタを上書きする前に、古いメモリをfreeする。

以下は、メモリリークを防ぐためのサンプルコードです。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *arr = (int *)malloc(5 * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "メモリの割り当てに失敗しました\n");
        return 1;
    }
    // 配列の使用
    for (int i = 0; i < 5; i++) {
        arr[i] = i;
    }
    // メモリの解放
    free(arr);
    return 0;
}

reallocの失敗時の対処法

realloc関数は、メモリの再割り当てに失敗することがあります。

この場合、元のメモリブロックはそのまま保持されますが、新しいメモリブロックのアドレスはNULLになります。

reallocの失敗時の対処法

  1. 新しいポインタを使う: reallocの結果を新しいポインタに代入し、NULLチェックを行う。
  2. 元のメモリを保持: reallocが失敗した場合、元のメモリを保持して適切に処理する。

以下は、reallocの失敗時の対処法を示すサンプルコードです。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *arr = (int *)malloc(5 * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "メモリの割り当てに失敗しました\n");
        return 1;
    }
    // 配列の使用
    for (int i = 0; i < 5; i++) {
        arr[i] = i;
    }
    // 配列のサイズを変更
    int *new_arr = (int *)realloc(arr, 10 * sizeof(int));
    if (new_arr == NULL) {
        fprintf(stderr, "メモリの再割り当てに失敗しました\n");
        free(arr); // 元のメモリを解放
        return 1;
    }
    // 新しい配列の使用
    for (int i = 5; i < 10; i++) {
        new_arr[i] = i;
    }
    // メモリの解放
    free(new_arr);
    return 0;
}

ポインタの誤使用によるバグの防止

ポインタの誤使用は、プログラムのクラッシュや予期しない動作を引き起こす原因となります。

特に動的メモリ割り当てを行う際には、ポインタの管理が重要です。

ポインタの誤使用の例

  1. 未初期化ポインタの使用: 初期化されていないポインタを使用する。
  2. 解放後のポインタの使用: freeしたメモリを指すポインタを再度使用する。

ポインタの誤使用を防ぐ方法

  1. ポインタの初期化: ポインタを宣言する際にNULLで初期化する。
  2. 解放後のポインタをNULLに設定: メモリを解放した後、ポインタをNULLに設定する。

以下は、ポインタの誤使用を防ぐためのサンプルコードです。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *arr = NULL; // ポインタの初期化
    arr = (int *)malloc(5 * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "メモリの割り当てに失敗しました\n");
        return 1;
    }
    // 配列の使用
    for (int i = 0; i < 5; i++) {
        arr[i] = i;
    }
    // メモリの解放
    free(arr);
    arr = NULL; // 解放後のポインタをNULLに設定
    return 0;
}

これらの対策を講じることで、動的メモリ割り当てに関連する多くの問題を防ぐことができます。

動的メモリ管理はC言語プログラミングにおいて重要なスキルであり、適切に扱うことで安定したプログラムを作成することができます。

まとめ

動的メモリ割り当ての重要性

C言語において、動的メモリ割り当ては非常に重要な技術です。

静的な配列では、プログラムの実行前に配列のサイズを決定する必要がありますが、動的メモリ割り当てを使用することで、実行時に必要なメモリを確保し、効率的にメモリを利用することができます。

これにより、メモリの無駄を減らし、プログラムの柔軟性を高めることができます。

動的メモリ割り当てを行うためには、malloccallocreallocといった関数を使用します。

これらの関数を適切に使用することで、必要なメモリを動的に確保し、プログラムの実行中に配列のサイズを変更することが可能になります。

しかし、その利点を最大限に活かすためには、適切なメモリ管理とエラーハンドリングが不可欠です。

これらのポイントを押さえて、効率的で柔軟なプログラムを作成しましょう。

目次から探す