SharpLab で理解する「参照型」のメモリ配置
― Book クラスと string フィールドを題材に
はじめに
C# の 参照型 (class) が実行時にどのようにヒープとスタックへ展開されるかを視覚的に確認できるツールとして、SharpLab には Inspect.* API が用意されています。本記事では次のコードを SharpLab で実行した際に表示される MemoryGraph を読み解き、参照・ヒープ・スタック・ガベージコレクション (GC) の関係を解説します。
注意:「参照型=ヒープ、値型=スタック」と単純に対応づけることはできません。配置は実行時の状況や JIT の最適化などに左右され、参照型への参照がヒープ上のオブジェクト内に置かれる場合や、値型の値がヒープ上のオブジェクトに含まれる場合もあります。SharpLab の図は、このコードを実行したときの一例として読みましょう。
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 は、Book インスタンスへの参照を表します。16 進数の表示は参照先を区別するための実装上の表現であり、C# のコードでアドレスを直接扱っていると考える必要はありません。
- このコードでは new を 2 回実行しているため、book1 と book2 はそれぞれ別の Book インスタンスを参照しています。
1-2. Book オブジェクト本体
- ヒープ上に 二つ の Book インスタンスが生成されます。
- 各インスタンスは
- header(同期ブロックなど)
- type handle(型情報を参照するための実装上の情報)
- フィールド Title(String への参照)をメンバーとして持ちます。
1-3. フィールド Title

- フィールド自体も 参照です。
- フィールド宣言で “" を代入しているため、初期状態の Title には空文字の String インスタンスへの参照が入っています。
- 代入後 (book1.Title = “吾輩は猫である") は、別の String インスタンスへの参照に切り替わります。Book オブジェクト本体のサイズは変わりません。
2. スクリーンショットの読み方(抜粋)
| SharpLab の枠 | 意味 | 注目点 |
|---|---|---|
| book1: Book ref(Stack) | スタックフレーム内のローカル変数 book1 | Book インスタンスへの参照。16 進数表示は参照先を区別するための実装上の表現 |
| Pointer to Book → Book at 0x… | 上記の参照先として表示される Book インスタンス | 同一プログラム内のすべての Book が同じ type handle を共有 |
| Title: String ref | Book のフィールド | 代入前は空文字インスタンス、代入後は新しいインスタンスを指す |
| 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);
- string は不変なので、連結結果は新しい String インスタンスになります。旧インスタンスへ到達可能な参照がなくなると、GC の回収対象になり得ます。
3-3. null と “" の違い
book2.Title = null;
Inspect.MemoryGraph(book2);
- Title フィールドが null 参照となり、空文字インスタンスとの違いを可視化できます。
4. ガベージコレクションとの関係
GC はスタック上の変数だけでなく、静的フィールドなどのルートからオブジェクトへの到達可能性をたどります。オブジェクトへ到達可能な参照がなくなると、回収対象になり得ます。ただし、すぐに回収されることを意味するわけではありません。
| 状態 | GC との関係 |
|---|---|
| book1 / book2 などから到達可能 | 到達可能な間は回収対象になりません |
| ある Book へ到達可能な参照がなくなる | その Book は回収対象になり得る |
| 共有された String へ到達可能な参照がすべてなくなる | その String も回収対象になり得る |
5. まとめ
- 参照型の変数はオブジェクトそのものではなく参照を保持する。実際の配置は実行状況によって異なるため、「参照型=ヒープ、値型=スタック」とは単純化しない
- フィールドが参照型なら、オブジェクトの中にも参照が入る
- 参照型の変数同士を代入すると参照がコピーされる。オブジェクト本体がコピーされるわけではなく、複数の変数が同じオブジェクトを参照できる
- string は不変で、内容変更は常に新インスタンス生成
- SharpLab の Inspect.* API を活用すると、こうした概念を視覚的に確認できる
次のステップ
- struct を使い 値型 と比較する
- List<T> のような コレクション を追加し、内部要素がどのようにヒープに配置されるか観察する
C# のメモリモデルを“頭の中の図”だけで理解するのは難しいものです。SharpLab で実行時の参照関係を実際に覗きながら学習すると、参照のコピー・string の不変性・GC の振る舞いが一気に腹落ちします。ぜひ授業や自習の場で試してみてください。











ディスカッション
コメント一覧
まだ、コメントがありません