動的メモリ管理

実行時にメモリを確保・解放。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++ では スマートポインタが標準