SharpLabを使ったC#のメモリ管理の確認
教科書で、変数には値型と参照型があると学ぶようになりますが、なかなかイメージが掴めないのではないでしょうか
次のようにツールを使って目で確認するのが一番です
SharpLab とは
SharpLab はブラウザ上で C# コードをコンパイル・実行できるプレイグラウンドです。
今回は Inspect.MemoryGraph() などの拡張機能を使い、変数がメモリ上でどう配置されるかを視覚化します。
補足
- Web ブラウザさえあれば Visual Studio を入れなくても動かせます。
- コード左側の IL / ASM タブは上級者向けなので、まずは Run ボタンと出力だけを見ると気が楽です。
- Inspect.* メソッドは SharpLab が提供する特別なユーティリティ。通常の C# プロジェクトには存在しません。
値型変数と参照型変数
int a = 3;
Inspect.MemoryGraph(a);
int[] b = new[]{1, 2, 3};
Inspect.MemoryGraph(b);
Inspect.MemoryGraph は 「変数がスタックに存在する値なのか、スタック上の参照なのか」を図解でズーム表示 する SharpLab 専用ツールです。
参照型 (b) は「スタック上の参照 → ヒープ上のオブジェクト」という 2 段構造を矢印付きで示し、メモリ配置の違いが一目で分かります。
値型 (a) はスタック上の実体をそのまま 1 ノードで表示。
補足
用語 | ざっくりイメージ | ポイント |
---|---|---|
値型(int 等) | 「中身がその場に入っている小箱」 | a そのものが 3 を保持。コピーすると 3 が複製される。 |
参照型(配列等) | 「中身が別倉庫にある鍵束」 | b には倉庫の住所(参照)だけが入る。コピーしても鍵束が増えるだけで、倉庫は同じ。 |
- C# では stack/heap の単語がよく出ますが、場所 より “実体が丸ごとそこにあるか” を意識すると理解しやすくなります。
- Inspect.MemoryGraph の矢印は「鍵束 → 倉庫」を示しています。

オブジェクトの変数(参照型)
using System;
Player player1 = new();
Inspect.MemoryGraph(player1);
Console.WriteLine($"{nameof(player1)}変数の内容");
Inspect.Stack(player1);
Console.WriteLine($"{nameof(player1)}のヒープ領域の内容");
Inspect.Heap(player1);
Inspect.Heap(player1.name);
class Player
{
public string name = "太郎";
public int age = 10;
}
Inspect.Stack(player1) は “デバッガのウォッチウィンドウを瞬間キャプチャする” 感覚のツールです。
まず どの変数がスタックにあり、何を指しているか をリストで把握し、必要に応じて Inspect.MemoryGraph でヒープ全体を可視化すると、値型と参照型の違い や スタック/ヒープの役割 が段階的に理解できます。
nameof(player1)とは
- nameof(player1) は 「player1」という識別子を安全に文字列化します。
- 例外・ログ・通知など「名前だけ欲しい」場面で .ToString() より適切。
- 使いこなすと 可読性と保守性が向上 するので、特に 定数文字列をベタ書きしていた部分 から置き換えてみると効果が大きいです。
補足
- new() で heap に箱(オブジェクト) を作り、その住所が player1 に入ります。
- Player クラス内部の string name 自体も参照型。二段階で heap を使う ことに注意。
- Inspect.Stack(player1) で表示されるのは「player1 という変数が stack 上にある」ところだけ。中身の nameや age は heap 側です。

オブジェクトの変数(内部に参照を持つ場合)
参照型の配列の要素がオブジェクトの場合
using System;
Player[] player = {
new Player() { name = "太郎", age = 10 },
new Player() { name = "太郎", age = 10 },
new Player() { name = "三郎", age = 10 },
};
Inspect.MemoryGraph(player);
Console.WriteLine($"{nameof(player)}スタック領域(保存アドレス)");
Inspect.Stack(player);
Console.WriteLine($"\n{nameof(player)}ヒープ領域(データ保存場所)");
Inspect.Heap(player);
Console.WriteLine($"\nplayer[0]ヒープ領域(データ保存場所)");
Inspect.Heap(player[0]);
Console.WriteLine($"player[0].name ヒープ領域(データ保存場所)");
Inspect.Heap(player[0].name);
Console.WriteLine($"\nplayer[1]ヒープ領域(データ保存場所)");
Inspect.Heap(player[1]);
Console.WriteLine($"player[1].name ヒープ領域(データ保存場所)");
Inspect.Heap(player[1].name);
Console.WriteLine($"\nplayer[2]ヒープ領域(データ保存場所)");
Inspect.Heap(player[2]);
Console.WriteLine($"player[2].name ヒープ領域(データ保存場所)");
Inspect.Heap(player[2].name);
class Player
{
public string name = "太郎";
public int age = 10;
}
補足
- 配列自体が参照型 – players は「3 個ぶんの倉庫が並ぶ大倉庫」の住所。
- 各要素 players[0] なども参照型 – それぞれが 個別の Player 倉庫 を指す。
- name フィールドはさらに string 倉庫を指す – 三階建て のような構造。
アドレスの並びから何がわかる?
- SharpLab 出力例では E0-05-3A-E6-9C-02 のように リトルエンディアン 表記。逆順に読むと実アドレスになります。
- 32 バイト刻みで並んでいることから「配列要素同士は隣接して確保される」ことがわかります。


playerヒープ領域(データ保存場所)で、player[0]のアドレスがE0-05-3A-E6-9C-02となっています
これからアドレスを求める場合、逆さまにする必要があります
つまり、02-9C-E6-3A-05-E0となります
同じようにplayer[1]とplayer[2]も変換し、引き算すると、32バイトの差があることがわかります
var x = 0x0600 - 0x05E0;
Console.WriteLine(x);
x = 0x0620 - 0x0600;
Console.WriteLine(x);
32
32
player[0]ヒープ領域(データ保存場所)のサイズをみると、32バイトになります
このことから、player[0]、player[1]、player[2]の実際のデータの格納場所は隣接していることがわかります
まとめ – 初学者のつまずきポイントと克服法
よくある疑問 | 説明 → 覚え方 |
---|---|
stack と heap が覚えづらい | 「使い捨ての作業机(stack)と、大きな倉庫(heap)」と置き換えて考える |
値型と参照型の違いがピンとこない | まず int と string の違いだけに絞り、string が参照型である理由をイメージで確認する |
アドレス逆読みが難しい | SharpLab の Copy as Address 機能で実アドレスをコピーし、16 進数計算サイトで差分を見ると理解しやすい |
ディスカッション
コメント一覧
まだ、コメントがありません