SharpLab で理解する「参照型」のメモリ配置

― Book クラスと string フィールドを題材に


はじめに

C# の 参照型 (class) が実行時にどのようにヒープとスタックへ展開されるかを視覚的に確認できるツールとして、SharpLab には Inspect.* API が用意されています。本記事では次のコードを SharpLab で実行した際に表示される MemoryGraph を読み解き、参照・ヒープ・スタック・ガベージコレクション (GC) の関係を解説します。

Book book1 = new Book();
Inspect.MemoryGraph(book1);     // ①
book1.Title = "吾輩は猫である";
Inspect.MemoryGraph(book1);     // ②
Inspect.Stack(book1);           // ③
Inspect.Heap(book1);            // ④

Book book2 = new Book();
Inspect.MemoryGraph(book2);     // ⑤
Inspect.Stack(book2);           // ⑥
Inspect.Heap(book2);            // ⑦

class Book
{
    public string Title = "";   // 明示的に空文字で初期化
}

ポイント

  • Inspect.MemoryGraph … 変数から到達可能なオブジェクトグラフ全体を描画
  • Inspect.Stack … スタックフレーム上のローカル変数を描画
  • Inspect.Heap … 対象オブジェクトのヒープ内レイアウトを描画

1. スタックとヒープに現れる構造

1-1. 変数 book1 / book2

  • スタックに置かれるのは「ポインタ値」。SharpLab ではbook1: Book ref といったラベルで枠が描かれ、16 進数の値が格納されています。
  • それぞれ 別のアドレスを保持しており、後述の Book 実体を指しています。

1-2. Book オブジェクト本体

  • ヒープ上に 二つ の Book インスタンスが生成されます。
  • 各インスタンスは
    • header(同期ブロックなど)
    • type handle(型情報へのポインタ)
    • フィールド Title(String への参照)をメンバーとして持ちます。

1-3. フィールド Title

  • フィールド自体も 参照です。
  • コンストラクタ内で “" を代入しているため、初期状態では 空文字を指す String へのポインタが入っています。
  • 代入後 (book1.Title = “吾輩は猫である") は 別の String インスタンスを指すようにポインタが更新され、Book のサイズは変わりません。

2. スクリーンショットの読み方(抜粋)

SharpLab の枠意味注目点
book1: Book ref(Stack)スタックフレーム内のローカル変数 book1中身はヒープ先頭アドレスへの 64bit 値
Pointer to Book → Book at 0x…上記ポインタが指すヒープ領域同一プログラム内のすべての Book が同じ type handle を共有
Title: String refBook のフィールド代入前は空文字インスタンス、代入後は新しいインスタンスを指す
String: 吾輩は猫であるString オブジェクト本体文字列は 不変 (immutable) なので書き換えではなく新規生成

3. 実験:SharpLab で確認してみよう

3-1. 参照共有の可視化

book2.Title = book1.Title;       // 同じ String を共有
Inspect.MemoryGraph(book1);
Inspect.MemoryGraph(book2);
  • どちらの Title も同一 String オブジェクトを指していることが図で確認できます。

3-2. 文字列の不変性

book2.Title = book2.Title + "(新装版)";
Inspect.MemoryGraph(book2);
  • 連結結果は新インスタンス。旧インスタンスは到達不能なら GC の対象になります。

3-3. null と “" の違い

book2.Title = null;
Inspect.MemoryGraph(book2);
  • Title フィールドが null 参照となり、空文字インスタンスとの違いを可視化できます。

4. ガベージコレクションとの関係

状態GC 対象になるか
book1 / book2 がスタックから参照されているならない
どちらかの変数が null になり、他に参照がないその Book と Title の String が 回収候補
同一 String を複数が共有し,全てが解放される共有された String も 回収候補

5. まとめ

  • 参照型オブジェクトはヒープに実体、スタックに参照 (ポインタ)
  • フィールドが参照型なら、オブジェクトの中にも参照が入る
  • 代入は参照先の付け替えであり、オブジェクト内にデータがコピーされるわけではない
  • string は不変で、内容変更は常に新インスタンス生成
  • SharpLab の Inspect.* API を活用すると、こうした概念を視覚的に確認できる

次のステップ

  • struct を使い 値型 と比較する
  • List<T> のような コレクション を追加し、内部要素がどのようにヒープに配置されるか観察する

C# のメモリモデルを“頭の中の図”だけで理解するのは難しいものです。SharpLab でスタックとヒープを実際に覗きながら学習すると、参照・値コピー・GC の振る舞いが一気に腹落ちします。ぜひ授業や自習の場で試してみてください。

訪問数 3 回, 今日の訪問数 3回