SharpLabを使ったC#のメモリ管理の確認

2025年7月31日

教科書で、変数には値型と参照型があると学ぶようになりますが、なかなかイメージが掴めないのではないでしょうか
次のようにツールを使って目で確認するのが一番です

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;
}

補足

  1. 配列自体が参照型 – players は「3 個ぶんの倉庫が並ぶ大倉庫」の住所。
  2. 各要素 players[0] なども参照型 – それぞれが 個別の Player 倉庫 を指す。
  3. 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 進数計算サイトで差分を見ると理解しやすい
訪問数 59 回, 今日の訪問数 1回

C#,学習,設計

Posted by hidepon