C++のrealloc関数を5つのサンプルコードで完全マスター

C++におけるrealloc関数の使い方を説明するコードサンプルの画像C++
この記事は約18分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事では、プログラミング言語C++におけるrealloc関数の使い方を初心者から中級者向けに解説します。

realloc関数はメモリ管理を効率的に行うための重要なツールです。

この関数の基本的な理解から応用技術まで、具体的なサンプルコードを交えて詳しくご紹介します。

読むことで、より安全で効率的なプログラムを書くためのスキルが身に付きます。

●realloc関数の基本

realloc関数とは、プログラム実行中に動的に確保したメモリ領域のサイズを変更するための関数です。

C++では、C言語の標準ライブラリに含まれており、<cstdlib>ヘッダファイルをインクルードすることで使用できます。

○realloc関数とは何か?

realloc関数は、もともと確保されたメモリブロックのポインタと新しいサイズを引数に取り、そのサイズに合わせてメモリブロックのサイズを調整します。

メモリの再割り当てが必要な場合に非常に役立ちます。成功すると、新しいメモリブロックのポインタを返し、失敗するとNULLを返します。

この挙動は、メモリ管理におけるエラーハンドリングを適切に行うために理解しておくべき重要なポイントです。

○realloc関数の基本的な文法と構造

realloc関数の文法は下記の通りです。

#include <cstdlib>  // realloc関数を使用するために必要

void* realloc(void* ptr, size_t new_size);
  • ptr -> 再割り当てするメモリブロックの元のポインタ
  • new_size -> 新しいサイズ(バイト単位)

実際にrealloc関数を使ったサンプルコードを見てみましょう。

#include <iostream>
#include <cstdlib>

int main() {
    int* ptr = (int*)malloc(10 * sizeof(int));  // 10個分のint型メモリを確保
    if (ptr == nullptr) {
        std::cerr << "Memory allocation failed." << std::endl;
        return -1;
    }

    // メモリサイズを20個分のint型に拡張
    int* resized_ptr = (int*)realloc(ptr, 20 * sizeof(int));
    if (resized_ptr == nullptr) {
        std::cerr << "Memory reallocation failed." << std::endl;
        free(ptr);  // 元のメモリを解放
        return -1;
    }

    // 新しいメモリ領域を使用
    for (int i = 0; i < 20; ++i) {
        resized_ptr[i] = i;
    }

    // 結果の出力
    for (int i = 0; i < 20; ++i) {
        std::cout << resized_ptr[i] << " ";
    }
    std::cout << std::endl;

    free(resized_ptr);  // メモリを解放
    return 0;
}

このコードでは、初めに10個分の整数を格納できるメモリを確保し、その後20個分に拡張しています。

reallocを使うことで、新旧のメモリブロックを効率的に管理できます。

この例では、reallocが成功した後、新しいポインタresized_ptrを使用してメモリにアクセスし、操作しています。

これはreallocの基本的な使い方であり、プログラミングにおいて非常に有効な技術です。

●realloc関数の詳細な使い方

realloc関数を使うことで、C++プログラミングにおけるメモリ管理の柔軟性が格段に向上します。

しかし、適切に使用しないとメモリリークや不正アクセスなどの問題を引き起こす可能性があるため、使い方をしっかりと理解し、適切に扱うことが重要です。

○サンプルコード1:基本的なメモリ割り当て

基本的なメモリ割り当ての例として、整数の配列を動的にリサイズする方法を見てみましょう。

初期のサイズを小さく始めて、必要に応じてサイズを増やす場面が多く存在します。

#include <iostream>
#include <cstdlib>

int main() {
    // 初期サイズとして5つの整数を格納できるメモリを確保
    int* array = (int*)malloc(5 * sizeof(int));
    if (array == nullptr) {
        std::cerr << "Initial memory allocation failed." << std::endl;
        return -1;
    }

    // 初期メモリブロックを使って何か処理を行う
    for (int i = 0; i < 5; i++) {
        array[i] = i * i;
    }

    // メモリブロックのサイズを10に拡大
    int* resized_array = (int*)realloc(array, 10 * sizeof(int));
    if (resized_array == nullptr) {
        std::cerr << "Memory reallocation failed." << std::endl;
        free(array);  // 元のメモリを解放
        return -1;
    }

    // 拡張された部分を使ってさらに処理を行う
    for (int i = 5; i < 10; i++) {
        resized_array[i] = i * i;
    }

    // 結果を表示
    for (int i = 0; i < 10; i++) {
        std::cout << resized_array[i] << " ";
    }
    std::cout << std::endl;

    free(resized_array);  // メモリを解放
    return 0;
}

この例では、初めに5つの整数を格納できるメモリブロックを確保し、後にそれを10に拡大しています。

reallocを使用することで、新たにメモリを確保し直すことなく、既存のデータを保持したままサイズ調整が可能です。

○サンプルコード2:メモリ割り当ての増加

プログラムが実行中にデータが増加する場面では、reallocを用いてメモリの増分割り当てが非常に有効です。

下記のコードは、ユーザー入力に応じて配列のサイズを段階的に増やす一例です。

#include <iostream>
#include <cstdlib>

int main() {
    int* data = (int*)malloc(sizeof(int));  // 最初は1つの整数分のメモリを確保
    int size = 1, input;

    while (std::cin >> input) {
        // 入力されたデータを格納
        data[size - 1] = input;
        // メモリサイズを1つ分増やす
        int* temp = (int*)realloc(data, (size + 1) * sizeof(int));
        if (temp == nullptr) {
            std::cerr << "Failed to expand memory." << std::endl;
            free(data);  // メモリを解放
            return -1;
        }
        data = temp;
        size++;
    }

    // 入力されたデータの表示
    for (int i = 0; i < size - 1; i++) {
        std::cout << data[i] << " ";
    }
    std::cout << std::endl;

    free(data);  // メモリを解放
    return 0;
}

このコードは、ユーザーからの入力を次々に受け取り、その都度メモリを1要素ずつ増やしています。

reallocがnullを返さない限り、データは失われることなく安全に拡張されます。

○サンプルコード3:メモリ割り当ての減少

逆に、使用しているメモリ量を減らす必要がある場合もreallocは役立ちます。

下記のサンプルでは、配列のサイズを縮小する方法を表しています。

#include <iostream>
#include <cstdlib>

int main() {
    int* data = (int*)malloc(10 * sizeof(int));  // 最初に10個分のメモリを確保
    if (data == nullptr) {
        std::cerr << "Initial memory allocation failed." << std::endl;
        return -1;
    }

    // データを格納
    for (int i = 0; i < 10; i++) {
        data[i] = i;
    }

    // メモリサイズを5に縮小
    int* resized_data = (int*)realloc(data, 5 * sizeof(int));
    if (resized_data == nullptr) {
        std::cerr << "Failed to reduce memory." << std::endl;
        free(data);  // メモリを解放
        return -1;
    }

    // 縮小されたメモリ領域の内容を表示
    for (int i = 0; i < 5; i++) {
        std::cout << resized_data[i] << " ";
    }
    std::cout << std::endl;

    free(resized_data);  // メモリを解放
    return 0;
}

この例では、最初に10個分のメモリを確保し、後にそれを5個分に縮小しています。

reallocを使うことで、不要なメモリ領域を効率的に解放し、システムリソースの無駄遣いを防ぐことが可能です。

●realloc関数の応用例

realloc関数は、単にサイズを調整するだけでなく、さまざまな応用シナリオに対応するための柔軟性を持っています。

プログラミングの現場では、動的なメモリ管理が必須であり、reallocを適切に使いこなすことが重要です。

ここでは、realloc関数の応用例として、動的配列のリサイズと複数のデータタイプを扱う場合の使用例を紹介します。

○サンプルコード4:動的配列のリサイズ

動的配列のリサイズは、realloc関数を使用する典型的なケースです。

下記の例では、ユーザーの入力に応じて整数配列のサイズを動的に調整する方法を表しています。

#include <iostream>
#include <cstdlib>

int main() {
    int* array = (int*)malloc(sizeof(int));  // 最初は1つの整数を格納できるメモリを確保
    int count = 0, input;
    std::cout << "Enter numbers (end with -1): ";
    while (std::cin >> input && input != -1) {
        int* temp = (int*)realloc(array, (count + 1) * sizeof(int));  // メモリをリサイズ
        if (temp == nullptr) {
            std::cerr << "Unable to allocate memory" << std::endl;
            free(array);
            return -1;
        }
        array = temp;
        array[count++] = input;
    }

    // リサイズされた配列の内容を出力
    for (int i = 0; i < count; i++) {
        std::cout << array[i] << " ";
    }
    std::cout << std::endl;

    free(array);  // 使用したメモリを解放
    return 0;
}

このサンプルコードは、ユーザーからの連続入力を受け取り、入力されるたびに配列のサイズを増やしています。

reallocを使用することで、必要に応じてメモリを増減させることが可能です。

○サンプルコード5:複数のデータタイプでの使用例

realloc関数は、異なるデータタイプのオブジェクトを含む配列を再配置するのにも使用できます。

例えば、構造体の配列サイズを調整する場合などです。

下記の例では、構造体を使用して、さまざまなデータを格納する配列のサイズを調整しています。

#include <iostream>
#include <cstdlib>

struct Data {
    int id;
    double value;
};

int main() {
    Data* data = (Data*)malloc(sizeof(Data));  // 最初は1つのData構造体を格納できるメモリを確保
    int count = 0;
    int idInput;
    double valueInput;
    std::cout << "Enter data pairs (id value), end with -1: ";
    while (std::cin >> idInput && idInput != -1) {
        std::cin >> valueInput;
        Data* temp = (Data*)realloc(data, (count + 1) * sizeof(Data));  // メモリをリサイズ
        if (temp == nullptr) {
            std::cerr << "Unable to allocate memory" << std::endl;
            free(data);
            return -1;
        }
        data = temp;
        data[count].id = idInput;
        data[count].value = valueInput;
        count++;
    }

    // リサイズされた配列の内容を出力
    for (int i = 0; i < count; i++) {
        std::cout << "ID: " << data[i].id << ", Value: " << data[i].value << std::endl;
    }

    free(data);  // 使用したメモリを解放
    return 0;
}

このコードでは、Data構造体の配列を動的にリサイズしています。

ユーザーからIDと値のペアを受け取り、それを配列に追加していきます。

reallocを使用することで、異なるデータタイプの動的管理が容易になります。

●realloc関数の注意点

realloc関数を使用する際にはいくつか重要な注意点があります。

特に、メモリリークの防止と不正なポインタの取り扱いには細心の注意を払う必要があります。

これらのポイントを理解し適切に対処することで、安全かつ効率的なプログラムを作成することができます。

○メモリリークの防止

realloc関数を使用するとき、新しいメモリの割り当てが失敗した場合には元のメモリブロックは解放されずに残ります。

この挙動は、メモリリークを引き起こす可能性があるため、非常に注意が必要です。

下記のコードは、reallocの使用例を示し、メモリリークを防ぐ方法を説明しています。

#include <iostream>
#include <cstdlib>

int main() {
    int* data = (int*)malloc(10 * sizeof(int));  // 10個分のint型メモリを確保
    if (data == nullptr) {
        std::cerr << "Memory allocation failed." << std::endl;
        return -1;
    }

    // メモリ再割り当てを試みる
    int* newData = (int*)realloc(data, 20 * sizeof(int));
    if (newData == nullptr) {
        std::cerr << "Failed to realloc memory." << std::endl;
        free(data);  // realloc失敗時は元のメモリを解放
        return -1;
    }

    data = newData;  // realloc成功時、新しいポインタで元のポインタを更新

    // 新しいメモリ領域を使用
    for (int i = 0; i < 20; ++i) {
        data[i] = i;
    }

    // 結果の出力
    for (int i = 0; i < 20; ++i) {
        std::cout << data[i] << " ";
    }
    std::cout << std::endl;

    free(data);  // 使用後のメモリを解放
    return 0;
}

このコードでは、reallocがNULLを返した場合には元のデータポインタをfree関数で明示的に解放しています。

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

○不正なポインタの取り扱い

realloc関数では、渡されたポインタがNULLでないこと、そして有効なヒープ領域を指していることが求められます。

不正なポインタ(野良ポインタやすでに解放された領域を指すポインタなど)をreallocに渡すと、ランタイムエラーが発生する可能性があります。

このようなエラーを避けるためには、ポインタが常に正しいオブジェクトまたはNULLを指していることを保証する必要があります。

●エンジニアなら知っておくべき豆知識

プログラミング、特にC++を用いた開発において、realloc関数はメモリ管理を効率化する強力なツールですが、その使用にはいくつかの注意点があります。

ここでは、realloc関数を取り巻く興味深い情報と、他のメモリ管理関数との比較、さらにはパフォーマンスへの影響について掘り下げていきます。

○realloc関数と他のメモリ管理関数との比較

C++におけるメモリ管理には、malloc、calloc、realloc、freeなど複数の関数が提供されています。

これらの関数は、それぞれに役割があり、適切な場面で利用することが重要です。

例えば、mallocは指定したバイト数のメモリブロックを割り当てるのに対し、callocはメモリブロックを割り当てと同時に0で初期化します。

reallocはこれらと異なり、既存のメモリブロックのサイズを変更する機能を持ちます。

これにより、プログラム実行中にデータ構造が変化しても、メモリを無駄なく利用できるようになります。

reallocは新しいメモリ領域が必要な場合には新たにメモリを割り当て、可能な場合には既存のメモリブロックを拡張します。

これにより、メモリの再利用が促進され、効率的なメモリ管理が可能になります。

しかし、reallocの使用には注意が必要です。

新しいメモリ割り当てが失敗した場合でも、元のメモリブロックは解放されず、メモリリークを引き起こす可能性があります。

また、reallocによるメモリの再割り当てプロセス中に、元のデータが新しい場所にコピーされる際、不必要なメモリコピーが発生することがあります。

○パフォーマンスへの影響

realloc関数のパフォーマンスへの影響は、使用状況によって大きく異なります。

reallocがメモリの再割り当てを行うプロセスは、新しいメモリブロックへのデータのコピーが伴うため、特に大きなデータを扱う場合には時間がかかることがあります。

このため、頻繁にサイズが変更されるようなデータ構造に対してreallocを用いる場合には、パフォーマンス低下を招く可能性があります。

一方で、reallocは必要に応じてメモリブロックを拡張または縮小するため、新しいメモリブロックの割り当てと既存データのコピーを最小限に抑えることができれば、全体的なメモリ使用効率を改善し、パフォーマンスを向上させることができます。

適切に使用すれば、メモリの断片化を防ぎ、長期間にわたるアプリケーションの実行においても高いパフォーマンスを維持する助けとなります。

まとめ

この記事では、C++におけるrealloc関数の使い方、注意点、および実践的なサンプルコードを通じてその詳細を解説しました。

realloc関数はメモリの再割り当てを効率的に行うための重要なツールであり、適切に使いこなすことでプログラムの柔軟性とパフォーマンスを大幅に向上させることができます。

ただし、使用には注意が必要であり、メモリリークや不正なポインタの取り扱いには特に注意を払う必要があります。

本記事が、参考になりましたら嬉しいです。