Inspect で “見るだけ体験”
― SharpLab と 20 行のコードで「値型」と「参照型」を理解しよう ―
1. なにをする?
- SharpLab(ブラウザだけで動く .NET プレイグラウンド)にそっくり貼り付けて Run ボタンを押すだけ。
- たった 1 本のプログラムで
- 値型 int のコピー
- 参照型 Person のコピーを Inspect で “メモリの中身” として可視化します。
- IL もアセンブリも一切読まなくて大丈夫。「見る」→「違いを感じる」 が今日のゴールです。
2. まずコードをコピペ!
https://sharplab.io で Code : C# / Result: Run を選び、
上部の選択行

下のコードを丸ごと貼り付けてください。
using System;
using SharpLab.Runtime; // Inspect が入っている名前空間
class Program
{
static void Main()
{
// ★ 値型のコピー
int a = 5;
int b = a; // 値そのものをコピー
b = 9;
Inspect.Stack(a); // スタック上の a
Inspect.Stack(b); // スタック上の b
// ★ 参照型のコピー
Person p = new Person { Name = "太郎" };
Person q = p; // 参照(住所)をコピー
q.Name = "次郎";
Inspect.MemoryGraph(p, q); // オブジェクト間の矢印を可視化
}
}
class Person
{
public string Name = "";
}
3. 出力をチェック
スタックとヒープという用語
用語の“日本語らしい”ひと言訳
英語 | よく使われるカタカナ表記 | 可能な和訳(意訳) | ニュアンス |
---|---|---|---|
Stack | スタック | 「積み重ね領域」 | 数値や一時データを“上に積んで、上から片づける”場所 |
Heap | ヒープ | 「動的確保領域」/「山(盛り)領域」 | 必要に応じて広さを決めて置く“大きな倉庫” |
- 「スタック」の“stack” は 積み重ねる という動詞から来ています。
- 「ヒープ」の “heap” は 山盛りに積んだ物 という名詞で、メモリを“好きな所に積む”イメージです。

- 左側「スタック」:メモを 上に積み上げ → 上から片づける イメージ
- 右側「ヒープ」:倉庫に 好きな場所へ箱を置き、住所ラベルで探しに行く イメージ
3.1 Inspect.Stack(値型)

Inspect.Stack(a)
┌─ Int32 (4 bytes)
│ 05 00 00 00 ← 5
└─
Inspect.Stack(b)
┌─ Int32
│ 09 00 00 00 ← 9
└─
ポイント: a と b は別々のバイト列=完全に独立
3.2 Inspect.MemoryGraph(参照型)


Person @000001A2F...
└─ field Name → String "次郎"
q ───┘
「ref」には 2 つの意味があるので整理しましょう
用語 | 何を指す? | 今回のサンプルとの関係 |
---|---|---|
参照 (reference)一般名詞としての ref | ヒープ上のオブジェクトまでの “住所メモ”。変数が格納している 4 or 8 byte の数値(ポインタ) | Person p = …; Person q = p; で p と q が持っている値そのもの。Inspect.MemoryGraph の矢印が可視化しているのがコレ |
ref キーワードメソッド引数修飾子 | 変数を「値ではなく 参照 として」メソッドに渡す C# の構文 | 今回のサンプルには 登場していない(static void Foo(ref int x) のように書くときだけ現れる) |
1. 今回の「参照 (reference)」=変数が持つ住所
Person p = new Person { Name = "太郎" };
Person q = p; // ← このコピーが「参照のコピー」
- p も q も 中身は「0x00007FFB…」のようなヒープ上のアドレス
- だから 1 つのオブジェクト(Person インスタンス)を共有する
- Inspect.MemoryGraph では
Person @00007FFB...
└─ field Name → String "次郎"
q ───┘
- という形で 「同じ住所を指している」 ことが矢印で示されます
2. refキーワードとは無関係
void Swap(ref int x, ref int y) { … }
- これは 引数として “変数そのもの” を渡す ための C# 構文
- 参照型/値型の区別とは別の次元の話
- 今回の 20 行サンプルでは ref キーワードを使っていないので、混同しなくて大丈夫 です
まとめ
- このサンプルで言う「ref」= reference(参照)
- 変数が保持している “オブジェクトへのポインタ” のこと
- p, q の中に入っているアドレス値
- ref キーワード はメソッド呼び出し時の特別な渡し方を指定するもの
- サンプルには出てこない別機能
「参照型だからコピーは“住所メモ”だけ」という仕組みをまず押さえ、その後 ref キーワードの学習に進むと混乱しません。
ポイント: p と q が 同じアドレス を指す一本の矢印=同じ家を共有
4. どう読めばいい?
型の種類 | コピー後に起こること | Inspect での見え方 |
---|---|---|
値型 (int, bool, struct など) | データをまるごと複製 | Inspect.Stack に 2 本の別バイト列 |
参照型 (class, string, 配列など) | ヒープ上のオブジェクトは 1 個だけで変数は「住所メモ」を共有 | Inspect.MemoryGraph で同じアドレスを指す矢印 |
覚え方はこれだけ👇
値型 = 中身をコピー
参照型 = 住所をコピー
5. もう一歩だけ試してみよう
コードの末尾に 1 行追加し、ヒープ確保(ボックス化)を覗くこともできます。
object boxed = a; // int を object に入れてヒープ確保
Inspect.Heap(boxed); // ← 確保先を直接ダンプ
Run すると System.Int32 が Gen0 ヒープに現れ、
「値型でも box するとヒープへ行く」 ことが体験できます。

部分 | 意味 | 備考 |
---|---|---|
System.Int32 | CLR 型名。ヒープ上にある boxed された int オブジェクト であることを表す | 値型 (int) を object へ代入するとヒープにラップされ、参照型として扱われる |
0x2273A29F450 | オブジェクトがヒープ上に配置されている 先頭アドレス(ポインタ) | 16 進数表記。ガベージコレクタが移動させない限り、このアドレスを起点に 24 B が確保されている |
つまり Inspect は
「ヒープのアドレス 0x2273A29F450 に、型 System.Int32 のオブジェクトが存在する」
と教えてくれています。
アドレス表記とエンディアン早わかりメモ
項目 | 内容 |
表示されるアドレス | 0x2273A29F450 のように 16 進数で上位バイト→下位バイト順 に列記。人が読むための表記で、エンディアンの影響は受けない。 |
実際のメモリ配列 | 64‑bit 環境はリトルエンディアンなので、RAM 上では 50 F4 29 A2 73 22 00 00 と 下位バイト→上位バイト の順で格納される。 |
CPU が参照するとき (Intel/AMD/ARM 共通) | バイト列をリトルエンディアン規則で組み立てて論理アドレス 0x0000002273A29F450 を得る。現在 .NET が公式サポートする主要アーキテクチャ(x86/x64 Intel & AMD、Arm64)は すべてリトルエンディアン。 |
覚え方 | 「画面に出るアドレスは固定、並び順はプラットフォーム依存」 と覚えれば混乱しない。 |
参考:その 24 B の内訳(64-bit CLR)
オフセット | サイズ | 内容 |
---|---|---|
+0x00 | 8 B | オブジェクトヘッダー(同期ブロック索引など) |
+0x08 | 8 B | メソッドテーブルへのポインタ(System.Int32 の型情報) |
+0x10 | 4 B | フィールド m_value (= 5) |
+0x14 | 4 B | パディング(8 B 境界調整) |
このように、たった 4 B の整数を boxing しただけで 24 B のヒープ領域が使われることが可視化できます。
スクリーンショットに写っているヒープ・ダンプを読むポイント
オフセット | ラベル | バイト列 (16 進) | 意味 |
---|---|---|---|
+0x00 | header | 00 00 00 00 00 00 00 00 | 同期ブロックインデックス と予備領域。マルチスレッド用ロック情報などが入るが、未使用なので 0。64-bit CLR では 8 byte 固定 |
+0x08 | type handle | 08 74 EB AF FA 7F 00 00 | メソッドテーブルポインタ(Method Table)。このアドレスが “このオブジェクトは System.Int32” であることを CLR に伝える |
+0x10 | m_value | 05 00 00 00 | 実際の整数値 (5)。リトルエンディアンなので下位バイトから並ぶ |
+0x14 | — | 00 00 00 00 | 8 byte 境界に合わせる パディング(Int32 フィールドは 4 byte なので余り 4 byte) |
64-bit CLR では ヘッダー 8 byte + タイプハンドル 8 byte + フィールド (4 byte) + パディング (4 byte) = 24 byte
以前の記事で Alloc Gen0 24 bytes System.Int32 と報告されたサイズと一致します。
画像内の青枠「m_value」
- 強調されている 05 00 00 00 が int の実値
- 値型を boxing すると、この 4 byte のために 最低 24 byte のヒープ領域 が取られる点が可視化できます
- 8 byte ヘッダー
- 8 byte メソッドテーブルポインタ
- 4 byte データ
- 4 byte パディング
ここから得られる学び
- 値型→object に代入 (boxing) すると「オブジェクトヘッダー + メタ情報 + 実データ」の形でヒープに置かれる
- 実データはたった 4 byte でも、ガーベジコレクタが扱う最小単位(24 byte)になるため 頻繁な boxing はコスト高
- Inspect.Heap は ヘッダー・type handle・フィールドを列挙 してくれるので、メモリの実レイアウトを視覚的に理解できる
この分析を 4.3 節(boxing の説明)に追加しておくと、
「なぜ値型をそのまま使った方がいいのか」
「box を避けるために Span<T> や ref struct が出てきた背景」
など、次の学習ステップへ自然に繋げられます。
Inspect.Heap(boxed)
┌─ System.Int32 @0000025C...
│ Size: 24 bytes Gen: 0
│ Value: 5
└─
→ 値型 int
を object
に入れて box すると、ヒープ (Gen0) に System.Int32
オブジェクトが作られ、その内部フィールド m_value
に 05 00 00 00
(= 5)が格納されていることが一目でわかります。
6. まとめ
- Inspect は Console.WriteLine 感覚で メモリをのぞき窓 に変える便利ツール。
- “値そのもの” と “住所メモ” の違いが 数字と矢印 で一目瞭然。
- あとは 変数名や値を変えて再実行 ⇢ 変化を観察してみよう。
見る → 触る → 納得する
コードは短くても、理解はグッと深まります。
ディスカッション
コメント一覧
まだ、コメントがありません