1. C言語の動的メモリ管理
void Test()
{
int* p1 = (int*)malloc(sizeof(int));
free(p1);
// malloc/calloc/realloc の違い
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int) * 10);
// p2 を free する必要はあるか?
free(p3);
}
realloc は元のメモリブロックを拡張(インプレース)するか、新しい領域にコピー(アウトオブプレース)します。インプレースの場合は p3 と p2 は同じアドレスを指し、アウトオブプレースの場合は元の領域が自動解放されるため、明示的に free(p2) する必要はありません。
2. C++ のメモリ管理方式
C++ では C 言語のメモリ管理関数も使用可能ですが、特にカスタム型の扱いが面倒な点があるため、new と delete 演算子が導入されました。
2.1 組み込み型に対する new/delete
void Test()
{
// 1つの int 領域を動的確保
int* ptr4 = new int;
// 1つの int 領域を確保し、値 10 で初期化
int* ptr5 = new int(10);
// 10個の int 領域を連続確保
int* ptr6 = new int[10];
delete ptr4;
delete ptr5;
delete[] ptr6;
}
単一要素の確保・解放には
new/deleteを、連続領域にはnew[]/delete[]を使用し、これらは必ず対にして使います。
2.2 カスタム型に対する new/delete
C 言語スタイルのノード生成
struct ListNode* CreateListNode(int val)
{
struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->_next = NULL;
newnode->_val = val;
return newnode;
}
C++ スタイルのノード生成
struct ListNode
{
ListNode* _next;
int _val;
ListNode(int val)
: _next(nullptr), _val(val)
{}
};
int main()
{
// カスタム型:領域確保+コンストラクタ呼び出し
// new 失敗時は例外がスローされるため、戻り値チェックは不要
ListNode* node1 = new ListNode(1);
ListNode* node2 = new ListNode(2);
ListNode* node3 = new ListNode(3);
// 後処理は省略
return 0;
}
C++ による連結リスト生成
// 番兵なしリストの生成
ListNode* CreateList(int n)
{
ListNode head(-1); // 番兵ノード
ListNode* tail = &head;
int val;
std::cout << "Please enter " << n << " node values: ";
for (size_t i = 0; i < n; ++i)
{
std::cin >> val;
tail->_next = new ListNode(val);
tail = tail->_next;
}
return head._next;
}
int main()
{
ListNode* list1 = CreateList(5);
// 実際のアプリケーションでは適切な解放処理が必要
return 0;
}
2.3 メモリ確保失敗時の挙動:例外 vs NULL
void func()
{
int n = 1;
while (true)
{
int* p = new int[1024 * 1024 * 100];
std::cout << n << " -> " << p << std::endl;
++n;
}
}
int main()
{
func();
return 0;
}
C 言語の
mallocは失敗時に NULL を返すため手動チェックが必要ですが、C++ のnewは失敗時にstd::bad_alloc例外をスローします。
2.4 カスタム型と new/delete の動作
class A
{
public:
A(int a = 0) : _a(a)
{ std::cout << "A():" << this << std::endl; }
~A()
{ std::cout << "~A():" << this << std::endl; }
private:
int _a;
};
class Stack
{
public:
Stack()
{
_a = (int*)malloc(sizeof(int) * 4);
_top = 0;
_capacity = 4;
}
~Stack()
{
free(_a);
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
A* ptr1 = new A; // operator new + 1回のコンストラクタ呼び出し
A* ptr2 = new A[10]; // operator new[] + 10回のコンストラクタ呼び出し
delete ptr1; // 1回のデストラクタ + operator delete
delete[] ptr2; // 10回のデストラクタ + operator delete[]
Stack* pst = new Stack;
delete pst;
int* p1 = new int[10];
delete[] p1;
return 0;
}
カスタム型に対して
newは領域確保後にコンストラクタを呼び出し、deleteはデストラクタを呼び出してから領域を解放します。malloc/freeではこの動作は行われません。
3. new/delete の内部実装
3.1 組み込み型の場合
組み込み型では new と malloc、delete と free の動作はほぼ同様ですが、new/new[] は単一要素・連続領域の区別があり、失敗時には例外を発生させる点が異なります。
3.2 カスタム型の場合
new の内部処理:
operator new関数を呼び出してメモリ領域を確保- 確保した領域上でコンストラクタを実行し、オブジェクトを初期化
delete の内部処理:
- 領域上でデストラクタを実行し、リソースを解放
operator delete関数を呼び出して領域を解放
new T[N] の内部処理:
operator new[]を呼び出し、その内部でoperator newを使って N 個分の領域を確保- 確保した領域上で N 回のコンストラクタを実行
delete[] の内部処理:
- 解放対象の領域上で N 回のデストラクタを実行
operator delete[]を呼び出し、その内部でoperator deleteを使って領域を解放