2.12.7. メモリ管理#

C/C++ で配列などを利用する場合、あらかじめデータ量を見積もり、それよりも大きなサイズの配列を宣言することが一般的です。しかし、プログラム実行中に予想以上に大きなデータが発生した場合、配列のサイズを拡張できなければすべてのデータを格納できなくなります。この問題への対策として、メモリを動的に割り当てたり拡張したりする方法があります。C 言語では mallocfree を、C++ 言語では newdelete を用いてメモリ管理を行います。

2.12.7.1. C 言語によるメモリ管理#

C 言語でメモリ領域を動的に確保するには、malloc 関数を利用します。malloc に確保したいメモリサイズを引数として与えると、その分のメモリ領域が確保され、その先頭アドレスへのポインタが返されます。OS やハードウェアの制限によりメモリが確保できない場合は、mallocNULL を返します。

C 言語にはガベージコレクションが存在しないため、malloc で確保したメモリは使用後に必ず free 関数で解放する必要があります。解放を行わない場合、確保したメモリは再利用されず、プログラムで連続して malloc を行うとメモリリークが発生します。結果として利用可能なメモリを使い果たし、プログラムが異常終了したりシステムが極端に遅くなったりする原因になります。

次の例では、malloc を使って int 型 10 個分のメモリ領域を確保し、そのアドレスを表示しています。メモリの確保に失敗した場合はエラーメッセージを表示します。最後に free を使って確保したメモリを解放しています。

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

int main() {

    int *p = (int *)malloc(sizeof(int) * 10);
    if (p == NULL) {
        printf("Memory cannot be allocated.");
    } else {
        printf("Memory has been allocated. The address is %p\n", p);
    }

    free(p);

    return 0;
}

malloc で確保したメモリ領域をさらに拡張したい場合は、realloc 関数を利用します。realloc の第 1 引数には malloc で確保したメモリ領域へのポインタを、第 2 引数には新しいメモリサイズを指定します。新しいメモリ領域が確保できれば、その先頭アドレスへのポインタが返され、確保できなければ NULL が返されます。

realloc は次のようにメモリの再確保を行います。

  1. 新しいメモリサイズが旧メモリ領域より小さい場合は、旧メモリ領域を縮小し、同じポインタを返します。新しいサイズが旧領域より大きい場合は、連続した空き領域に新しいメモリを確保します。確保できなければ NULL を返します。

  2. 旧メモリ領域のデータを新しいメモリ領域にコピーします。コピー量は旧領域のサイズに依存します。

  3. 旧メモリ領域を解放します。

  4. 新しいメモリ領域へのポインタを返します。

再確保に失敗した場合、旧メモリ領域は解放されません。そのため、必要に応じて自分で free を呼び出す必要があります。次の例では、最初に確保した int 型 10 個分の領域を 20 個分に拡張しています。

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

int main() {

    int *p = (int *)malloc(sizeof(int) * 10);
    if (p == NULL) {
        printf("Memory cannot be allocated.");
    } else {
        printf("Memory has been allocated. The address is %p\n", p);
    }

    int *q = (int *)realloc(p, sizeof(int) * 20);
    if (q == NULL) {
        printf("Memory cannot be enlarged.");
    } else {
        printf("Memory has been enlarged. The new address is %p\n", q);
    }
    
    free(q);

    return 0;
}

2.12.7.2. C++ 言語によるメモリ管理#

C++ では、メモリの確保に new、解放に delete を使用します。delete で解放できるのは new で確保したメモリのみであり、mallocrealloc で確保したメモリを delete で解放することは未定義動作です。

未定義動作は危険です。何も起きないこともあれば、爆発することも。

#include <iostream>
#include <memory>

int main () {
    
    int* p = nullptr;
    try {
        p = new int[10];
        std::cout << p << std::endl;
    } catch (const std::bad_alloc& e) {
        std::cout << "Memory cannot be allocated." << std::endl;
    }
    
    delete[] p;
    
    return 0;
}