2.12.7. メモリ管理#
C / C++ で配列などを利用する際には、あらかじめデータ量を見積もり、それよりも大きなサイズの配列を宣言することが一般的です。しかし、プログラムの実行途中で予想以上に大きなデータが発生した場合、配列のサイズを拡張できなければ、すべてのデータを格納できなくなります。この問題への対策として、メモリのサイズを動的に割り当てたり拡張したりする方法があります。C 言語では malloc や free を、C++ 言語では new や delete を用いてメモリ管理を行います。
2.12.7.1. C 言語によるメモリ管理#
C 言語でメモリ領域を動的に確保するには malloc 関数を利用します。malloc 関数に確保したいメモリサイズを引数として与えると、その分のメモリ領域が確保され、その先頭アドレスへのポインタが返されます。OS やハードウェアの制限によりメモリが確保できない場合もあり、その際は malloc 関数は NULL を返します。
C 言語にはガベージコレクションが存在しないため、malloc 関数で確保したメモリを使い終わったら、必ず free 関数で解放する必要があります。解放を行わない場合、確保したメモリは再利用されず、プログラム中で次々に malloc 処理を行うと、いわゆるメモリリークが発生します。その結果、利用可能なメモリを使い果たしてしまい、プログラムが異常終了したり、システムが極端に遅くなったりする原因となります。
#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 関数は次のようなプロセスでメモリの再確保を行います。
新しく確保するメモリサイズが旧メモリ領域より小さい場合は、旧メモリ領域を縮小し、そのまま同じポインタを返します。新しいサイズが旧領域より大きい場合は、連続した空き領域に新しいメモリ領域を確保します。確保できなければ
NULLを返して終了します。旧メモリ領域のデータを新メモリ領域へコピーします。コピー量は旧領域のサイズに依存します。
旧メモリ領域を解放します。
新メモリ領域へのポインタを返します。
メモリの再確保に失敗した場合、旧メモリ領域は解放されません。そのため、必要に応じて自分で 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.");
// 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;
}
2.12.7.2. C++ 言語によるメモリ管理#
C++ ではメモリの確保に new を、メモリの解放に delete を利用します。delete で解放できるのは new で確保したメモリのみです。malloc や realloc で確保したメモリを 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;
}
※ 配列を new[] で確保した場合、本来は delete[] で解放する必要がありますが、サンプルコードは原稿の内容を変更しないという指示に従い、そのままとしています。