動的メモリ管理
実行時にメモリを確保・解放。new と delete の使い方
概要
これまで学んだ変数は、 サイズがコンパイル時に決定されていました。
しかし実際のアプリケーションでは、 実行時にサイズが決まるデータが必要です。 そこで活躍するのが動的メモリ管理です。
スタックとヒープ
メモリは2つの領域に分かれています:
| スタック | ヒープ | |
|---|---|---|
| 割り当て方法 | 自動 | new で手動 |
| 解放方法 | 自動 | delete で手動 |
| サイズ | 小さい | 大きい |
| 速度 | 高速 | やや遅い |
| 書き方 | int x = 42; | int* p = new int(42); |
new と delete
単一の値を確保
#include <iostream>
int main() {
// ヒープにメモリを確保
int* ptr = new int(42);
std::cout << "値: " << *ptr << std::endl;
// 使用終了後、メモリを解放
delete ptr;
ptr = nullptr; // 解放後は nullptr に
return 0;
}
値: 42
重要:newで確保したメモリは、
必ずdeleteで解放する必要があります。
配列を確保
#include <iostream>
int main() {
int size = 5;
int* arr = new int[size]; // 5個の要素を確保
// 初期化
for (int i = 0; i < size; i++) {
arr[i] = (i + 1) * 10;
}
// 使用
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
delete[] arr; // 配列の場合は[]を付ける
arr = nullptr;
return 0;
}
10 20 30 40 50
重要:配列を確保したときは、
delete[](括弧なし)で解放します。
メモリリーク
メモリリークは、 確保したメモリを解放し忘れる深刻なバグです。
悪い例
void badFunction() {
int* ptr = new int(100);
std::cout << *ptr << std::endl;
// delete ptr; を忘れた!
// メモリリーク発生
}
良い例
void goodFunction() {
int* ptr = new int(100);
try {
std::cout << *ptr << std::endl;
} catch (...) {
delete ptr; // 例外安全性
throw;
}
delete ptr;
}
実践例
例1: 動的な学生管理
#include <iostream>
int main() {
int numStudents;
std::cout << "学生数: ";
std::cin >> numStudents;
int* scores = new int[numStudents];
// スコアを入力
for (int i = 0; i < numStudents; i++) {
std::cout << "学生 " << (i + 1) << " のスコア: ";
std::cin >> scores[i];
}
// 平均を計算
int sum = 0;
for (int i = 0; i < numStudents; i++) {
sum += scores[i];
}
std::cout << "平均スコア: " << (sum / (double)numStudents) << std::endl;
delete[] scores;
return 0;
}
例2: 2次元配列を動的に確保
#include <iostream>
int main() {
int rows = 3, cols = 4;
// 2次元配列を動的に確保
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
// 値を入れる
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = i * 4 + j + 1;
}
}
// 表示
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
std::cout << matrix[i][j] << " ";
}
std::cout << std::endl;
}
// メモリを解放
for (int i = 0; i < rows; i++) {
delete[] matrix[i];
}
delete[] matrix;
return 0;
}
1 2 3 4
5 6 7 8
9 10 11 12
スマートポインタ(Modern C++)
C++11以降、スマートポインタが導入されました。 これは、メモリ管理を自動化します。
#include <iostream>
#include <memory>
int main() {
// unique_ptr:唯一の所有者
std::unique_ptr<int> ptr1(new int(42));
std::cout << *ptr1 << std::endl;
// スコープを抜けるとき、自動的に delete
// shared_ptr:複数の所有者が可能
std::shared_ptr<int> ptr2 = std::make_shared<int>(100);
std::cout << *ptr2 << std::endl;
// 最後の所有者がスコープを抜けるとき、自動的に delete
return 0;
}
42
100
スマートポインタを使えば、 delete し忘れ(メモリリーク)の心配がないので、 modern C++ではこちらが推奨されます。
ポイント
- 動的メモリ管理で実行時にサイズを決めたデータを扱える
- newで確保、deleteで解放
- 配列は
new[]で確保、delete[]で解放 - メモリリークに注意(必ず delete する)
- Modern C++ では スマートポインタの使用が推奨
よくある誤り
誤り1: delete し忘れ
int* ptr = new int(42);
std::cout << *ptr << std::endl;
// delete ptr; を忘れた! → メモリリーク
誤り2: new と delete[]、delete[]と new の組み合わせミス
int* arr = new int[5];
delete arr; // エラー!: delete[] を使うべき
int* ptr = new int(42);
delete[] ptr; // エラー!: delete を使うべき
誤り3: double delete
int* ptr = new int(42);
delete ptr;
delete ptr; // 危険!: 2度目の delete は未定義動作
やってみよう
練習1: 実行時ユーザーが指定した個数の整数を入力し、合計を計算。
練習2: 動的に確保した配列をソートするプログラム。
練習3: 2次元動的配列を作成し、逆行列のような操作。
チャレンジ: スマートポインタを使ったメモリ管理の実装。
まとめ
- 動的メモリ管理は C++の重要な機能
- new/deleteの対を使う必要がある
- メモリリークは深刻なバグ
- Modern C++ では スマートポインタが標準