はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#】fixedステートメントを用いてGCによるアドレスの変更を阻止する(GC管理下だとコンパクションの可能性があるため)

はじめに

今回はfixedステートメントについて書きたいと思います。

概要

GC管理下にあるオブジェクトは、アドレスが最適化のために移動されることがあります。詳細は未確認飛行さんのコンパクションの箇所を読むと良いでしょう。
ufcpp.net

unmanagedポインター(参照型変数や参照渡し以外)はコンパクションによるアドレスの変更に対応していないので、アドレスを固定させるという操作が必要になってしまいます。そこで利用するのがfixedステートメントです。

fixed ステートメントを使うと、ガベージ コレクターによる移動可能変数の再配置を防ぎ、その変数へのポインターを宣言することができます。 固定 (またはピン留め) された変数のアドレスは、そのステートメントの実行中に変わりません。 宣言されたポインターは、対応する fixed ステートメント内でのみ使用できます。 宣言されたポインターは読み取り専用であり、変更できません。

fixed ステートメント - 移動可能変数を固定する - C# | Microsoft Learn

使い方

public struct SampleStruct
{
    public int Value;
}
public class SampleClass
{
    public int Value;
}
    
public static void Main(string[] args)
{
    unsafe
    {
        // スタック上に配置されるのでfixedは必要ない
        int x = 1;
        int* pX = &x; 
            
        // 参照型を含まない構造体もスタック上に配置されるのでfixedはいらない
        SampleStruct sample = new SampleStruct();
        SampleStruct* p = &sample;
    }

    unsafe
    {
        // sampleはヒープに配置されるのでGC管理下
        var sample = new SampleClass { Value = 1 };
        fixed (int* p = &sample.Value)
        {
            // 1
            Console.WriteLine(*p);
        }
    }

    unsafe
    {
        // 配列もヒープに配置されるのでGC管理下
        Span<int> span = Enumerable.Range(0, 10).ToArray().AsSpan();

        fixed (int* p = span)
        {
            for (var i = 0; i < span.Length; i++)
            {
                // 0, 1, 2, 3, 4, 5, 6, 7 ,8, 9
                Console.WriteLine(p[i]);
            }
        }
    }

    unsafe
    {
        // 文字列もヒープに配置されるのでGC管理下
        string message = "Hello!";
        fixed (char* p = message)
        {
            // H
            Console.WriteLine(*p);
                
            // e
            // NOTE: 「*(p + 1)」と同じ意味
            Console.WriteLine(p[1]);
        }
    }
}