C++におけるメモリ管理 (パート1)
メモリ管理はC++プログラミングの重要な側面です。PythonやJavaなどの自動メモリ管理を持つ言語とは異なり、C++はメモリの割り当てと解放の責任を開発者の手に委ねています。これにより、プログラムのメモリに対する細かい制御が可能になりますが、メモリリークやバッファーオーバーフローなどのメモリ関連のバグのリスクも導入されます。
目次
(1) メモリ管理の紹介
(1.i) メモリ管理とは何ですか?
(1.ii) スタック (フレーム)とヒープの比較
(1.iii) メモリ管理におけるポインタの役割
(2) 動的メモリ割り当て
(2.i) newおよびdelete演算子
(2.ii) newとmalloc()の違い
(3) スマートポインタ
(3.i) std::unique_ptr、std::shared_ptr、およびstd::weak_ptr
(3.ii) スマートポインタを使用した自動メモリ管理
(3.iii) スマートポインタによるメモリリークの回避
(4) 配列のメモリ管理
(4.i) 動的配列と固定サイズの配列
(4.ii) New[]およびdelete[]演算子
(4.iii) オブジェクトの配列の管理
(5) メモリ所有権とリソース管理
(5.i) RAII(Resource Acquisition Is Initialization)イディオム
(5.ii) クラスを使用したカスタムリソース管理
(6) メモリリーク
(6.i) メモリリークの理解
(6.ii) ツールを使用したメモリリークの検出とデバッグ
(6.iii) メモリリークを防ぐためのテクニック
(7) メモリセーフティ
(7.i) バッファーオーバーフローとスタックスマッシング
(7.ii) 安全な文字列処理
(7.iii) ヌルポインタのデリファレンスを回避
(8) メモリ最適化テクニック
(8.i) メモリ断片化の削減
(8.ii) カスタムメモリ割り当て子
(8.iii) コンパイラの最適化
(9) 並行性とメモリ
(9.i) スレッドセーフティとデータ競合
(9.ii) 共有メモリの管理のための同期プリミティブ
(9.iii) メモリバリアとアトミック操作
(10) メモリ管理のベストプラクティス
(10.i)確立されたC++ベストプラクティスに従う
(10.ii) メモリ管理の決定を文書化する
(10.iii) 継続的なテストとプロファイリング
今記事では、上記の目次から最初の 2 つの項目について議論したいと思います。
(1) メモリ管理の紹介
(1.i) メモリ管理とは何ですか?
プログラムのデータと変数に対するメモリリソースの割り当てと解放のプロセスを、プログラミングにおいてメモリ管理と呼びます。C++ではメモリに直接制御権があるため、メモリ管理は重要な責任となります。
(1.ii) スタック(フレーム)とヒープの比較
C++では、メモリを主に2つの領域に分けることができます:スタック(フレーム)とヒープ。
スタック (フレーム):スタック (フレーム)は、比較的短い寿命を持つ変数(関数のパラメータ、ローカル変数、戻り値のアドレスなど)を格納するためのメモリ領域です。これはLast-In, First-Out(LIFO)の方法で動作し、最も最近追加されたアイテムが最初に削除されるということを意味します。スタックメモリはコンパイラによって自動的に管理され、変数はスコープに入ったり出たりする際に作成および破棄されます。これにより、スタックメモリは高速で効率的ですが、サイズが制限されています。
ヒープ:ヒープは、プログラムの実行中に動的に作成されるオブジェクトなど、潜在的に長い寿命を持つオブジェクトを格納するための柔軟なメモリ領域です。スタックと異なり、ヒープ上のメモリの割り当てと解放は明示的にプログラマによって制御されます。これにより、元のスコープを超えて存続するオブジェクトのためにメモリを割り当てることができます。ただし、この権限には、メモリリークの回避や解放済みメモリへのアクセスを防ぐためのメモリ管理の責任が伴います。
(1.iii) メモリ管理におけるポインタの役割
C++では、メモリ管理においてポインタが中心的な役割を果たします。ポインタは他のオブジェクトのメモリアドレスを格納する変数です。ポインタを操作することで、ヒープ上にメモリを割り当て、データにアクセスして変更し、不要になったメモリを解放することができます。
メモリ割り当て:new演算子(またはC言語のmalloc())を使用してヒープ上にメモリを割り当て、割り当てられたメモリへのポインタを取得できます。
メモリ解放:動的に割り当てられたメモリが不要になった場合、delete演算子(またはC言語のfree())を使用して明示的に解放する必要があります。メモリを解放しないと、メモリリークが発生します。
ポインタ演算:ポインタ演算を使用してメモリを直接操作および変更できます。ただし、メモリの境界を越えないよう注意深く操作する必要があります。
リソース管理:ポインタはファイル、ネットワーク接続、またはその他のシステムリソースの管理など、リソース管理に使用されます。適切なリソース管理は、不要になったときにリソースが解放されることを確実にします。
(2) 動的メモリ割り当て
C++における動的メモリ割り当ては、実行時にヒープ上にメモリを割り当てる基本的な概念であり、関数やブロックのスコープに厳密に結びつかないオブジェクトを作成し管理することを可能にします。これにより、メモリ使用の柔軟性と制御が向上します。
(2.i) new演算子とdelete演算子
C++では、ヒープ上でメモリの割り当てと解放をそれぞれ行うために、new演算子とdelete演算子を使用できます。
newによる割り当て:new演算子はヒープ上のオブジェクトのためにメモリを割り当てるために使用されます。これは動的に割り当てられたメモリへのポインタを返します。
例えば:
この例では、dynamicIntegerは動的に割り当てられたメモリ内に存在するint型へのポインタです。
deleteによる解放:動的に割り当てられたメモリが不要になった場合、明示的にdelete演算子を使用して解放する必要があります。メモリを解放しないと、メモリリークが発生します。
次は、動的に割り当てられた整数を解放する方法です:
delete演算子はメモリを解放するだけでなく、クラス型の場合はデストラクタを呼び出して適切なクリーンアップを行います。
(2.ii) newとmalloc()の違い
newとdeleteはC++用の演算子であり、型の安全性を提供します。型に基づいて割り当てられたメモリのサイズを自動的に決定し、サイズに関連するエラーを導入しにくくします。
malloc()とfree()はC用の関数であり、メモリのサイズを明示的に指定する必要があります。サイズが正しく計算されない場合、エラーが発生する可能性があります。
この例では:
newを使用して整数のメモリを割り当てています。dynamicIntegerNewに対してはnewを、dynamicIntegerMallocに対してはmalloc()を使用して整数のメモリを割り当てています。
両方の動的に割り当てられた整数に値を代入します。
動的に割り当てられた整数の値を表示します。
メモリを解放するために、deleteをdynamicIntegerNewに対して、free()をdynamicIntegerMallocに対して使用します。
主な違い:
newは型(この場合はint)に基づいて自動的に割り当てるメモリのサイズを決定するため、明示的にsizeof(int)を指定する必要はありません。malloc()ではサイズを明示的に指定する必要があります。
newは正しい型(この場合はint*)の割り当てられたメモリへのポインタを返すため、キャストは必要ありません。malloc()はvoid*を返すため、int*にキャストする必要があります。
メモリを解放する際、newは関連付けられたメモリがクラス型と関連付けられている場合、適切なクリーンアップを行う方法を提供するため、デストラクタも呼び出します。free()はC関数であり、デストラクタを呼び出しません。
C++での一般的でおすすめされる使用方法は、型の安全性とC++の機能との統合が良いため、newを使用することです。malloc()はC関数であり、Cライブラリと一緒に作業するか、歴史的な理由から使用されることがあります。
今記事では(1) メモリ管理の紹介と(2) 動的メモリ割り当てについて議論いたしました。次回の記事(C++におけるメモリ管理 (パート2))では、(3) スマートポインタと(4) 配列のメモリ管理について詳しく掘り下げ、お届けいたします。お楽しみにお待ちいただければ幸いです。
エンジニアファーストの会社 株式会社CRE-CO
su_myat_phyu
この記事が気に入ったらサポートをしてみませんか?