new[]とdeleteの不一致が「たまたま動いてしまう」危険性
new[]で確保した配列に対して、delete[]ではなく単体用のdeleteを使ってしまう不一致です。未定義動作であるにもかかわらず、int型のような単純な配列ではクラッシュせずに動いてしまうことがあります。
なぜエラーが出ないのか
出力: done(クラッシュせず動いてしまう)
(エラーなし)- C++は何も報告しません。コンパイルも実行も正常に完了するためです
出力: done(クラッシュせず動いてしまう)- 実際の挙動 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 #include <iostream> 2 int main() { 3 int *arr = new int[5]; 4 delete arr; ^ 5 std::cout << "done" << std::endl; 6 return 0; 7 }
new[]で確保した配列にはdelete[]を使うのが正しい対応です。単体用のdeleteを使うのは未定義動作ですが、int型のような単純な配列ではクラッシュせずに動いてしまうことがあります。動いたからといって正しいわけではありません。
直し方: delete arr; を delete[] arr; にします。
パターン2
1 #include <iostream> 2 int main() { 3 double *vals = new double[10]; 4 delete vals; ^ 5 std::cout << "done" << std::endl; 6 return 0; 7 }
「クラッシュしないなら問題ない」というのは危険な誤解です。オブジェクトの配列で同じミスをすると、配列内の最初の要素以外のデストラクタが呼ばれないなど、より深刻な形で表面化することがあります。
直し方: delete vals; を delete[] vals; にします。
パターン3
1 #include <iostream> 2 int main() { 3 char *buf = new char[64]; 4 delete buf; ^ 5 std::cout << "done" << std::endl; 6 return 0; 7 }
new[]とdeleteの対応、およびnewとdelete[]の対応、どちらの組み合わせ違いも未定義動作です。確保に使った形と解放に使う形は必ず揃える必要があります。
直し方: delete buf; を delete[] buf; にします。
パターン4
1 #include <iostream> 2 int main() { 3 long *nums = new long[8]; 4 delete nums; ^ 5 std::cout << "done" << std::endl; 6 return 0; 7 }
long型の配列でも同じです。new[]で確保したらdelete[]で解放します。
直し方: delete nums; を delete[] nums; にします。
パターン5
1 #include <iostream> 2 int main() { 3 float *data = new float[20]; 4 delete data; ^ 5 std::cout << "done" << std::endl; 6 return 0; 7 }
要素数が増えても規則は同じです。確保と解放の形を必ず揃えます。
直し方: delete data; を delete[] data; にします。
よくある誤解
「クラッシュしないなら問題ない」というのは危険な誤解です。この不一致は未定義動作であり、動くかどうかは処理系や型(デストラクタの有無など)に依存します。オブジェクトの配列で同じミスをすると、配列内の最初の要素以外のデストラクタが呼ばれないなど、より深刻な形で表面化することがあります。
実務での勘所
多くの実装では、new[]は配列の要素数をポインタの直前に隠れたヘッダとして書き込んでおり、delete[]はそのヘッダを読んで「何個のデストラクタを呼ぶべきか」を判断します。単体用のdeleteはこのヘッダの存在を知らないため、最初の1要素分のデストラクタしか呼ばずに、誤った前提でメモリを解放してしまいます。ただしこれはあくまで典型的な実装上の振る舞いであり、C++の規格はnew[]とdeleteの不一致を単に「未定義動作」としか定めていません。動くように見える場合も規格上の保証は一切無い、という理解が重要です。