[C言語] グローバル変数で動的な配列を生成する方法

C言語では、グローバル変数として動的な配列を生成することは直接的にはできません。

しかし、ポインタを使用してヒープメモリ上に配列を動的に確保することが可能です。

具体的には、グローバルスコープでポインタを宣言し、プログラムの初期化段階でmalloc関数を用いてメモリを確保します。

この方法により、プログラム全体でアクセス可能な動的配列を実現できます。

使用後はfree関数でメモリを解放することが重要です。

この記事でわかること
  • グローバル変数として動的配列を生成する手順
  • 動的配列を用いたプログラムの実践例
  • 文字列やデータベースなどへの動的配列の応用方法
  • メモリリークを防ぐための注意点
  • realloc失敗時の対処法

目次から探す

グローバル変数で動的配列を生成する手順

グローバル変数の宣言

グローバル変数は、プログラム全体でアクセス可能な変数です。

動的配列をグローバル変数として宣言することで、どの関数からでもその配列にアクセスできます。

以下は、グローバル変数として動的配列を宣言する例です。

#include <stdio.h>
#include <stdlib.h>
// グローバル変数としての動的配列
int *dynamicArray;

この例では、dynamicArrayというポインタをグローバル変数として宣言しています。

このポインタを使って、動的にメモリを確保した配列を管理します。

mallocを用いた動的配列の生成

malloc関数を使用して、動的にメモリを確保し、配列を生成します。

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

以下に、mallocを用いた動的配列の生成例を示します。

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

このプログラムでは、mallocを使って10個の整数を格納できるメモリを確保し、配列を初期化して表示しています。

配列のサイズ変更とreallocの活用

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

reallocは、既存のメモリブロックのサイズを変更し、新しいサイズのメモリブロックの先頭アドレスを返します。

以下に、reallocを用いた配列のサイズ変更例を示します。

#include <stdio.h>
#include <stdlib.h>
int *dynamicArray;
int main() {
    int size = 10;
    dynamicArray = (int *)malloc(size * sizeof(int));
    if (dynamicArray == NULL) {
        printf("メモリの確保に失敗しました。\n");
        return 1;
    }
    // 配列のサイズを20に変更
    size = 20;
    dynamicArray = (int *)realloc(dynamicArray, size * sizeof(int));
    if (dynamicArray == NULL) {
        printf("メモリの再確保に失敗しました。\n");
        return 1;
    }
    // 新しい要素を初期化
    for (int i = 10; i < size; i++) {
        dynamicArray[i] = i;
    }
    // 配列の内容を表示
    for (int i = 0; i < size; i++) {
        printf("%d ", dynamicArray[i]);
    }
    printf("\n");
    // メモリの解放
    free(dynamicArray);
    return 0;
}

このプログラムでは、reallocを使って配列のサイズを10から20に変更し、新しい要素を初期化して表示しています。

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

動的メモリを使用する際には、メモリリークを防ぐことが重要です。

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

以下の点に注意して、メモリリークを防ぎましょう。

  • 確保したメモリは必ずfree関数で解放する。
  • mallocreallocの戻り値を必ずチェックし、NULLでないことを確認する。
  • reallocを使用する際は、元のポインタを直接上書きせず、一時的なポインタを使って安全に再確保する。

これらの注意点を守ることで、メモリリークを防ぎ、プログラムの安定性を向上させることができます。

実践例:グローバル変数で動的配列を使ったプログラム

ここでは、グローバル変数として動的配列を使用する具体的なプログラム例を示します。

このプログラムでは、配列の初期化、値の代入、要素数の動的変更、内容の表示、メモリの解放を行います。

配列の初期化と値の代入

まず、動的配列を初期化し、値を代入します。

以下のコードでは、mallocを使用してメモリを確保し、配列に値を代入しています。

#include <stdio.h>
#include <stdlib.h>
// グローバル変数としての動的配列
int *dynamicArray;
void initializeArray(int size) {
    // メモリの動的確保
    dynamicArray = (int *)malloc(size * sizeof(int));
    // メモリ確保の成功確認
    if (dynamicArray == NULL) {
        printf("メモリの確保に失敗しました。\n");
        exit(1);
    }
    // 配列の初期化
    for (int i = 0; i < size; i++) {
        dynamicArray[i] = i * 2; // 値の代入
    }
}

この関数では、指定されたサイズの配列を初期化し、各要素に2倍の値を代入しています。

配列の要素数を動的に変更する

次に、reallocを使用して配列の要素数を動的に変更します。

以下のコードでは、配列のサイズを変更し、新しい要素を初期化しています。

void resizeArray(int newSize) {
    // 配列のサイズを変更
    dynamicArray = (int *)realloc(dynamicArray, newSize * sizeof(int));
    // メモリ再確保の成功確認
    if (dynamicArray == NULL) {
        printf("メモリの再確保に失敗しました。\n");
        exit(1);
    }
    // 新しい要素を初期化
    for (int i = newSize / 2; i < newSize; i++) {
        dynamicArray[i] = i * 2; // 値の代入
    }
}

この関数では、配列のサイズを2倍にし、新しい要素に値を代入しています。

配列の内容を表示する

配列の内容を表示するための関数を作成します。

以下のコードでは、配列の全要素を表示しています。

void displayArray(int size) {
    printf("配列の内容: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", dynamicArray[i]);
    }
    printf("\n");
}

この関数は、配列の全要素を順に表示します。

メモリの解放とプログラムの終了

最後に、動的に確保したメモリを解放し、プログラムを終了します。

以下のコードでは、freeを使用してメモリを解放しています。

void freeMemory() {
    // メモリの解放
    free(dynamicArray);
    dynamicArray = NULL; // ポインタをNULLに設定
}
int main() {
    int initialSize = 5;
    int newSize = 10;
    // 配列の初期化と値の代入
    initializeArray(initialSize);
    displayArray(initialSize);
    // 配列の要素数を動的に変更
    resizeArray(newSize);
    displayArray(newSize);
    // メモリの解放
    freeMemory();
    return 0;
}

このプログラムでは、初期サイズ5の配列を作成し、サイズを10に変更した後、メモリを解放しています。

freeMemory関数でメモリを解放し、ポインタをNULLに設定することで、メモリリークを防いでいます。

応用例

動的配列は、さまざまなプログラムで応用可能です。

ここでは、いくつかの応用例を紹介します。

文字列の動的管理

動的配列を使用することで、文字列の長さを動的に管理できます。

以下の例では、reallocを用いて文字列の長さを変更しています。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *dynamicString;
void manageString(const char *initialString) {
    // 初期文字列の長さを取得
    int length = strlen(initialString);
    // メモリの動的確保
    dynamicString = (char *)malloc((length + 1) * sizeof(char));
    if (dynamicString == NULL) {
        printf("メモリの確保に失敗しました。\n");
        exit(1);
    }
    // 文字列のコピー
    strcpy(dynamicString, initialString);
    // 文字列の表示
    printf("初期文字列: %s\n", dynamicString);
    // 文字列の長さを変更
    dynamicString = (char *)realloc(dynamicString, (length + 10) * sizeof(char));
    if (dynamicString == NULL) {
        printf("メモリの再確保に失敗しました。\n");
        exit(1);
    }
    // 文字列の追加
    strcat(dynamicString, " 追加文字");
    // 文字列の表示
    printf("変更後の文字列: %s\n", dynamicString);
    // メモリの解放
    free(dynamicString);
}

このプログラムでは、初期文字列に追加文字を付加し、動的に管理しています。

動的配列を用いたデータベースの実装

動的配列を用いることで、データベースのレコードを動的に管理できます。

以下の例では、簡単なデータベースを実装しています。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
    int id;
    char name[50];
} Record;
Record *database;
int recordCount = 0;
void addRecord(int id, const char *name) {
    // レコード数を増やす
    database = (Record *)realloc(database, (recordCount + 1) * sizeof(Record));
    if (database == NULL) {
        printf("メモリの再確保に失敗しました。\n");
        exit(1);
    }
    // 新しいレコードを追加
    database[recordCount].id = id;
    strcpy(database[recordCount].name, name);
    recordCount++;
}
void displayDatabase() {
    printf("データベースの内容:\n");
    for (int i = 0; i < recordCount; i++) {
        printf("ID: %d, Name: %s\n", database[i].id, database[i].name);
    }
}
int main() {
    addRecord(1, "Alice");
    addRecord(2, "Bob");
    displayDatabase();
    // メモリの解放
    free(database);
    return 0;
}

このプログラムでは、動的配列を用いてレコードを追加し、データベースを管理しています。

動的配列を用いたファイル入出力

動的配列を使用することで、ファイルの内容を動的に読み込むことができます。

以下の例では、ファイルからデータを読み込み、動的配列に格納しています。

#include <stdio.h>
#include <stdlib.h>
int *fileData;
int dataSize = 0;
void readFile(const char *filename) {
    FILE *file = fopen(filename, "r");
    if (file == NULL) {
        printf("ファイルを開けませんでした。\n");
        exit(1);
    }
    int value;
    while (fscanf(file, "%d", &value) != EOF) {
        fileData = (int *)realloc(fileData, (dataSize + 1) * sizeof(int));
        if (fileData == NULL) {
            printf("メモリの再確保に失敗しました。\n");
            exit(1);
        }
        fileData[dataSize++] = value;
    }
    fclose(file);
}
void displayFileData() {
    printf("ファイルデータ: ");
    for (int i = 0; i < dataSize; i++) {
        printf("%d ", fileData[i]);
    }
    printf("\n");
}
int main() {
    readFile("data.txt");
    displayFileData();
    // メモリの解放
    free(fileData);
    return 0;
}

このプログラムでは、data.txtというファイルから整数を読み込み、動的配列に格納しています。

動的配列を用いたソートアルゴリズムの実装

動的配列を用いることで、ソートアルゴリズムを実装することができます。

以下の例では、バブルソートを用いて配列をソートしています。

#include <stdio.h>
#include <stdlib.h>
void bubbleSort(int *array, int size) {
    for (int i = 0; i < size - 1; i++) {
        for (int j = 0; j < size - i - 1; j++) {
            if (array[j] > array[j + 1]) {
                int temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
}
int main() {
    int size = 5;
    int *array = (int *)malloc(size * sizeof(int));
    if (array == NULL) {
        printf("メモリの確保に失敗しました。\n");
        return 1;
    }
    // 配列の初期化
    array[0] = 5;
    array[1] = 3;
    array[2] = 8;
    array[3] = 1;
    array[4] = 2;
    // ソート前の配列を表示
    printf("ソート前: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
    // バブルソートの実行
    bubbleSort(array, size);
    // ソート後の配列を表示
    printf("ソート後: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");
    // メモリの解放
    free(array);
    return 0;
}

このプログラムでは、動的配列を用いてバブルソートを実装し、配列をソートしています。

動的配列を用いたグラフデータ構造の実装

動的配列を用いることで、グラフデータ構造を実装することができます。

以下の例では、隣接リストを用いたグラフの実装を示します。

#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
    int vertex;
    struct Node *next;
} Node;
typedef struct {
    Node **adjLists;
    int numVertices;
} Graph;
Node *createNode(int vertex) {
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->vertex = vertex;
    newNode->next = NULL;
    return newNode;
}
Graph *createGraph(int vertices) {
    Graph *graph = (Graph *)malloc(sizeof(Graph));
    graph->numVertices = vertices;
    graph->adjLists = (Node **)malloc(vertices * sizeof(Node *));
    for (int i = 0; i < vertices; i++) {
        graph->adjLists[i] = NULL;
    }
    return graph;
}
void addEdge(Graph *graph, int src, int dest) {
    Node *newNode = createNode(dest);
    newNode->next = graph->adjLists[src];
    graph->adjLists[src] = newNode;
    newNode = createNode(src);
    newNode->next = graph->adjLists[dest];
    graph->adjLists[dest] = newNode;
}
void printGraph(Graph *graph) {
    for (int v = 0; v < graph->numVertices; v++) {
        Node *temp = graph->adjLists[v];
        printf("\n 頂点 %d: ", v);
        while (temp) {
            printf("%d -> ", temp->vertex);
            temp = temp->next;
        }
        printf("NULL\n");
    }
}
int main() {
    int vertices = 5;
    Graph *graph = createGraph(vertices);
    addEdge(graph, 0, 1);
    addEdge(graph, 0, 4);
    addEdge(graph, 1, 2);
    addEdge(graph, 1, 3);
    addEdge(graph, 1, 4);
    addEdge(graph, 2, 3);
    addEdge(graph, 3, 4);
    printGraph(graph);
    // メモリの解放は省略
    return 0;
}

このプログラムでは、動的配列を用いて隣接リスト形式のグラフを実装し、グラフの構造を表示しています。

よくある質問

グローバル変数で動的配列を使う際の利点は?

グローバル変数で動的配列を使用する利点は、プログラム全体で配列にアクセスできることです。

これにより、複数の関数で同じ配列を共有し、データを一元管理することが可能になります。

また、動的配列を使用することで、必要に応じて配列のサイズを変更できるため、メモリの効率的な利用が可能です。

メモリリークを防ぐためにはどうすれば良い?

メモリリークを防ぐためには、以下の点に注意することが重要です。

  • 動的に確保したメモリは、使用が終わったら必ずfree関数で解放する。
  • mallocreallocの戻り値を必ずチェックし、NULLでないことを確認する。
  • reallocを使用する際は、元のポインタを直接上書きせず、一時的なポインタを使って安全に再確保する。
  • プログラムの終了時に、すべての動的メモリを解放する。

これらの注意点を守ることで、メモリリークを防ぎ、プログラムの安定性を向上させることができます。

reallocで失敗した場合の対処法は?

reallocでメモリの再確保が失敗した場合、NULLが返されます。

この場合、元のメモリブロックはそのまま保持されるため、データは失われません。

対処法としては、以下の手順を行います。

  1. reallocの戻り値を一時的なポインタに保存し、NULLでないことを確認する。
  2. NULLであれば、メモリの再確保に失敗したことを示すエラーメッセージを表示し、適切にプログラムを終了する。
  3. NULLでなければ、一時的なポインタを元のポインタに代入し、処理を続行する。

例:int *temp = realloc(array, newSize * sizeof(int)); if (temp == NULL) { /* エラー処理 */ } else { array = temp; }

まとめ

動的配列をグローバル変数として使用することで、プログラム全体で効率的にデータを管理できます。

振り返ると、動的配列の生成、サイズ変更、メモリリークの防止方法について学びました。

これらの知識を活用し、より効率的で安定したプログラムを作成してみてください。

  • URLをコピーしました!
目次から探す