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

2026年8月28日

広告

― 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)スタックフレーム内のローカル変数 book1Book インスタンスへの参照。16 進数表示は参照先を区別するための実装上の表現
Pointer to Book → Book at 0x…上記の参照先として表示される Book インスタンス同一プログラム内のすべての Book が同じ type handle を共有
Title: String refBook のフィールド代入前は空文字インスタンス、代入後は新しいインスタンスを指す
String: 吾輩は猫であるString オブジェクト本体文字列は 不変 (immutable) なので書き換えではなく新規生成

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

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

book2.Title = book1.Title;       // 同じ String を共有
Inspect.MemoryGraph(book1);
Inspect.MemoryGraph(book2);
参照型の変数同士を代入すると参照がコピーされ、book1とbook2が同じBookインスタンスを参照する図
参照がコピーされると、複数の変数が同じオブジェクトを参照できます。オブジェクト本体がコピーされるわけではありません。
  • どちらの 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 の振る舞いが一気に腹落ちします。ぜひ授業や自習の場で試してみてください。

訪問数 123 回, 今日の訪問数 1回

広告