メモリの動的割り当て

C/C++ で配列などを利用する時に、データをある程度見積もって、それよりも大きいサイズの配列を宣言する。しかし、プログラムを実行している途中で、予想外に大きなデータが生じた時に、配列のサイズを拡大しなければ、すべてのデータを格納できなくなる。この対策として、メモリのサイズを動的に割り当てたり、拡大したりする。C 言語では mallocfree などで、C++ 言語では newdelete などでメモリの管理を行う。

C 言語によるメモリ管理

メモリ領域の確保

C 言語でメモリ領域を動的に確保するときに malloc 関数を利用する。malloc 関数に確保したいメモリのサイズを引数に指定すると、その分のメモリ領域が確保され、そのメモリ領域へのポインタが返される。OS による制限やハードウェアによる制限などで、メモリが確保できない場合も想定される。そのとき、malloc 関数は NULL を返す。

C 言語にはガベージコレクションがないので、malloc 関数で確保したメモリが使わなくなったら、free 関数で解放する必要がある。メモリの解放を行わない場合に、メモリの再利用が行われない。そのためプログラムで、メモリの解放を行わないで次々と mallloc 処理を行ったとき(メモリリーク)、いずれ使用可能なメモリ量を使い果たしてしまい、プログラムが異常終了したり、システムが極端に重くなったりする。

#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. 新メモリ領域へのポインタを返す。

realloc 関数のメモリ再確保のプロセスのように、メモリの再確保に失敗した場合は、旧メモリ領域は解放されないので、必要に応じて自分で解放する必要がある。次のサンプルコードは、はじめに確保した 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.");
        // free `p`, if required
    } 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 %q\n", q);
    }
    
    free(q);

    return 0;
}

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

C++ ではメモリの確保は new を利用し、メモリの解放は delete を利用する。delete で解放できるのは new で確保したメモリだけである。mallocrealloc 関数で確保したメモリを delete する場合は定義されていないので、何が起こるのかはわからない。

include <iostream>
include <memory>

// g++ sample.cpp -std=c++0x

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

References

  • メモリの動的な確保と解放. 薫の Hack
  • C,C++におけるメモリの扱い方について(初心編). Qiita