C#のメモリ領域

2024年1月26日

メモリ管理の概要について考えてみましょう

メモリ領域の種類

実際の仕組みは非常に複雑になっています
用語もたくさんあり、概略、かつ簡易にまとめるためにマイクロソフトの内部ドキュメントと一致しないところもありますので、学習用の参考としてください

スタック領域

スタック領域は、メソッド呼び出しに関連して使用されるメモリ領域で、呼び出し元のメソッドに戻るために必要な情報(例えば、呼び出し元のメソッドが実行していたプログラムカウンタ)を保存します。スタック領域は、LIFO(Last In First Out)というデータ構造に基づいて管理され、スタックのトップにあるデータが最後に取り出されます。スタック領域は、メソッドが呼び出されるたびに確保され、メソッドから返るときに解放されます。スタックは、高速なアクセスが可能であり、データのサイズが小さいため、ここにデータを格納することができます。また、ローカル変数やメソッドの引数もここに置かれます

ヒープ領域

 コード

プログラムのインストラクションが格納されるメモリ領域で、プログラムが実行されるために必要な命令が保持され、CPUがアクセスして実行することができます

静的データ

スタティックフィールドや、スタティックメソッドなどが格納されます。アプリケーションが終了するまでメモリが保持されます。インスタンスデータとは違い、クラスごとのデータになります
格納場所は、メソッドテーブルと呼ばれる領域の最後になります
各クラスとインターフェイスは、AppDomainにロードされると、メソッドテーブルデータ構造によってメモリに表現されます。これは、オブジェクトの最初のインスタンスが作成される前のクラスロードアクティビティの結果です。オブジェクト(インスタンス)は状態を表しますが、メソッドテーブルは動作を表します

インスタンスデータ

ヒープ領域は、プログラムが動的にメモリを確保するために使用される領域で、長期間にわたってデータを保持する必要がある場合に使用されます。例えば、クラスのインスタンス(new 演算子の実行)、配列、文字列などの参照型の変数の実体がここに格納されます。ヒープ領域は、プログラムが実行される間中、使用され続けます

クラスと構造体、列挙型のメモリ配置について

構造体

スタック上に格納されるケース:

ローカル変数として宣言された場合:

メソッド内でローカル変数として構造体のインスタンスが宣言された場合、そのインスタンスはスタック上に格納されることが一般的です。これは、メソッドの実行スコープに限定された生存期間を持つためです。

ヒープ上に格納されるケース

クラスのフィールドとして宣言された場合

クラスのフィールドとして構造体のインスタンスが宣言されている場合、そのインスタンスはクラスのインスタンスがヒープ上に配置されるのと同様に、ヒープ上に格納されます。

配列やコレクション内に存在する場合

構造体のインスタンスが配列やリストなどのコレクション内に格納される場合、これらのコレクションはヒープ上に配置されるため、構造体のインスタンスもヒープ上に配置されます。

ボクシングが発生した場合

構造体のインスタンスがオブジェクト型にキャストされるなどしてボクシングが発生した場合、ボクシングされた構造体はヒープ上に格納されます。

C#において構造体のインスタンスがスタック上に格納されるかヒープ上に格納されるかは、その使用方法に依存します。マネージド言語であるC#では、具体的なメモリの配置はCLR(Common Language Runtime)によって管理されるため、プログラマが直接コントロールすることは少ないです。プログラムの設計やパフォーマンス最適化を考える際には、これらの挙動を理解しておくことが重要です。

クラス

ヒープ領域

クラスのインスタンスは動的に割り当てられ、ヒープ領域に配置されます。ヒープは、実行時にサイズが変動するデータや長期間存在するデータを格納するためのメモリ領域です。C#のガーベジコレクタは、不要になったオブジェクトを自動で検出し、ヒープ領域から解放します。

スタック領域

スタック領域は、メソッドの呼び出しとともに割り当てられるメモリで、主にメソッドのローカル変数やメソッドの呼び出し情報(戻り値のアドレス、パラメータなど)を格納します。クラスのインスタンスへの参照も、メソッドのローカル変数としてスタック上に配置されることがありますが、インスタンス自体はヒープ上に存在します。

C#におけるクラスのインスタンスは、ヒープ領域に格納され、インスタンスへの参照がスタック領域に格納されることが一般的です。このメモリ管理メカニズムにより、C#では複雑なメモリ管理タスクを開発者から抽象化し、安全で効率的なプログラミングを可能にしています。

列挙型

C#における列挙型(enum)は、値型(struct)の一種であり、プログラム内で一連の定数をより読みやすく表現するために使用されます。列挙型は、数値(通常は整数)に名前を付けることにより、コードの可読性と保守性を向上させることができます。

列挙型のメモリ格納

列挙型のインスタンスは、その基となる型に基づいてメモリに格納されます。C#のenumはデフォルトでint型として扱われますが、bytesbyteshortushortintuintlong、またはulongなど、他の整数型を基にすることもできます。列挙型は値型であるため、そのインスタンスはスタック上に格納されるか、または構造体やクラスのフィールドとしてヒープ上に格納されるオブジェクトの一部となるかに依存します。

スタティックフィールドとインスタンスフィールド
(スタティックプロパティ、インスタンスプロパティ)

class Player
{
    public static int count = 3;
    public int score = 5;
}

メモリ管理の違い

スタティックフィールド

C#でのスタティックフィールドは、.NETのランタイムによって管理されるヒープ領域の静的データを保存するエリアに置かれます。

インスタンスフィールド

一方、インスタンスフィールドは、インスタンスが作成されると、そのインスタンスに対応する領域がヒープ上に確保され、そのインスタンスが破棄されると解放されます。

スタティックフィールドがメモリに展開されるタイミング

C#でスタティックフィールドは、そのクラスが最初に使用されるタイミングで確保されます。これは、C#のランタイムによって「クラスの最初の使用」として定義されています。「クラスの最初の使用」は、そのクラスが以下のいずれかの方法で初めて参照される時点を指します。

  • スタティックメンバーが呼び出される
  • インスタンスが作成される
  • 静的コンストラクタが呼び出される

これにより、スタティックフィールドは、プログラム起動時には確保されなくても、クラスが最初に使用されるタイミングで確保されます。

ただし、スタティックフィールドは、プログラム終了まで解放されず、次回のプログラム起動時もその値が使用される。

また、スタティックフィールドは、プログラムのライフタイム中、常に存在し、アクセス可能です。

参考

詳細資料

上記の記載は、この資料に基づいています
ただし、原本は非常に難易度が高いため、なるべく簡易に理解できるようにしています
ですから、資料と一致していないところもありますので、大枠で捉えてください
詳細レベルで把握したい場合、原本を拝読することをお勧めします

この記事では、以下について説明されています
• SystemDomain、SharedDomain、DefaultDomain
• オブジェクトレイアウトおよびその他のメモリの詳細
• メソッドテーブルレイアウト
• メソッドディスパッチ

イラストでまとめたもの

初学者の学習用に作成したものです
現実の実装とは一部異なることがありますので、ご了承ください

C#,学習

Posted by hidepon