0Pricing
C# Academy · レッスン

安全でないコード、ポインター、固定バッファー

unsafe キーワードを使い、ポインターを操作し、fixed で管理メモリを固定し、構造体内の固定長バッファーにアクセスします。

「安全でないコード、ポインター、固定バッファー」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。

unsafeコードとは

C#のunsafeキーワードを使うと、生ポインター、ポインター演算、固定長バッファーなど、メモリを直接操作できるようになります。その代わり、GCによる安全性の保証を回避します。プロジェクトファイルに<AllowUnsafeBlocks>true</AllowUnsafeBlocks>を設定して有効にします。

// Must add to .csproj:
// <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

// Unsafe blocks can appear inside methods:
unsafe
{
    int x = 42;
    int* ptr = &x;          // get address of x
    Console.WriteLine(*ptr); // dereference — prints 42
    *ptr = 100;
    Console.WriteLine(x);    // 100 — x was mutated via pointer
}

C#のポインター型

ポインターの構文はC/C++に似ています。T*はTへのポインターです。アンマネージド値型(int、double、参照フィールドを持たない構造体)へのポインターを宣言できます。マネージド型へのポインターは使用できません。

unsafe
{
    int    i = 10;
    double d = 3.14;

    int*    ip = &i;
    double* dp = &d;

    // Dereference with *
    Console.WriteLine(*ip); // 10

    // Pointer arithmetic — move to next int in memory
    int[] arr = { 1, 2, 3 };
    fixed (int* p = arr)
    {
        Console.WriteLine(*(p + 0)); // 1
        Console.WriteLine(*(p + 1)); // 2
        Console.WriteLine(*(p + 2)); // 3
    }
}

fixedステートメント

マネージドオブジェクトはGCによって移動されることがあります。マネージドオブジェクトのアドレスを取得するには、fixedを使ってピン留めする必要があります。fixedブロックの実行中は、GCによってオブジェクトが移動されません。

byte[] buffer = new byte[256];

unsafe
{
    fixed (byte* pBuf = buffer)
    {
        // pBuf is valid only inside this block
        // GC won't relocate 'buffer' here
        for (int i = 0; i < buffer.Length; i++)
            pBuf[i] = (byte)i;
    }
    // After 'fixed', GC can move 'buffer' again
}

Console.WriteLine(buffer[5]); // 5

ポインター演算

ポインターに整数を加算したり、減算したりできます。int*に1を加えると、4バイト(sizeof int)分進みます。これは高速な大容量メモリ操作の基礎となります。

unsafe
{
    int[] data = { 10, 20, 30, 40, 50 };
    fixed (int* start = data)
    {
        int* p = start;
        long sum = 0;
        for (int i = 0; i < data.Length; i++)
        {
            sum += *p;
            p++;    // advance by sizeof(int) = 4 bytes
        }
        Console.WriteLine(sum); // 150

        // Or with index syntax:
        Console.WriteLine(start[2]); // 30
    }
}

stackalloc:スタック上のメモリ確保

stackallocはヒープではなくスタック上にメモリブロックを確保します。GCへの負荷がなく、ピン留めも必要ありません。現在のメソッドのスタックフレーム内に限られるため、メソッドが戻るとメモリは自動的に解放されます。

// Stack-allocated buffer — zero heap allocation
Span<int> buffer = stackalloc int[128]; // safe Span wrapper
buffer.Fill(0);
buffer[0] = 42;
Console.WriteLine(buffer[0]); // 42

// Or raw pointer form (requires unsafe):
unsafe
{
    int* raw = stackalloc int[128];
    raw[0] = 99;
    Console.WriteLine(raw[0]); // 99
}
// Stack frame popped — memory gone

構造体内の固定長バッファー

固定長バッファーは、構造体内に固定長配列をインラインで埋め込みます。ヒープアロケーションも追加のポインターも必要ありません。unsafeコンテキストで宣言した構造体内でfixed修飾子を使用します。

unsafe struct NetworkPacket
{
    public int  Length;
    public byte Command;
    public fixed byte Payload[256]; // 256 bytes inline in the struct
}

unsafe
{
    NetworkPacket pkt;
    pkt.Length  = 10;
    pkt.Command = 0x01;

    // Fill payload inline — no heap allocation
    for (int i = 0; i < 10; i++)
        pkt.Payload[i] = (byte)i;

    Console.WriteLine(pkt.Payload[5]); // 5
}
// sizeof(NetworkPacket) = 4 + 1 + 256 = ~261 bytes on stack

void*とキャスト

void*は型を持たないポインターで、汎用的なメモリ操作に便利です。参照解除する前に、型付きポインターへキャストする必要があります。sizeof(T)演算子は、unsafeコンテキストでアンマネージド型に対して使用できます。

unsafe
{
    int value = 42;
    void* vp = &value;      // typeless pointer
    int*  ip = (int*)vp;    // cast back to int*
    Console.WriteLine(*ip); // 42

    // sizeof works on unmanaged types
    Console.WriteLine(sizeof(int));    // 4
    Console.WriteLine(sizeof(double)); // 8
    Console.WriteLine(sizeof(long));   // 8
}

MemoryMarshalとUnsafeクラス

System.Runtime.CompilerServices.UnsafeとSystem.Runtime.InteropServices.MemoryMarshalは、低レベル操作の多くに対して、キーワードを使わない安全な代替手段を提供します。Spanと互換性があり、unsafeキーワードなしで使用できます。

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// Reinterpret a Span<byte> as Span<int> — no copy, no unsafe keyword
byte[] raw = new byte[16];
Span<int> ints = MemoryMarshal.Cast<byte, int>(raw);
ints[0] = 12345678;
Console.WriteLine(ints[0]); // 12345678

// Unsafe.As<,> — reinterpret reference type (advanced)
ref int first = ref MemoryMarshal.GetReference(ints);
Unsafe.Add(ref first, 1) = 99999;
Console.WriteLine(ints[1]); // 99999

unsafeを使う場合(使わない場合)

unsafeコードは、明確なパフォーマンス上の必要性があり、安全な代替手段がない場合に限って使用します。unsafeな範囲は最小限に抑え、privateなヘルパーメソッドや専用の型に分離します。網羅的なテストを作成し、前提条件をドキュメント化します。

// GOOD: narrow unsafe scope
public static unsafe int SumBytes(ReadOnlySpan<byte> data)
{
    int total = 0;
    fixed (byte* p = data)
    {
        byte* end = p + data.Length;
        for (byte* cur = p; cur < end; cur++)
            total += *cur;
    }
    return total;
}

// BETTER for modern code: use SIMD via Vector<T> or hardware intrinsics
// which stay in managed code but still achieve native-level perf

実践:バイナリプロトコルの解析

unsafeポインターと固定長バッファーは、構造体のレイアウトをwire formatに正確に一致させる必要があるバイナリプロトコルの解析で特に役立ちます。バイトバッファーを構造体へ直接キャストできるため、コピーは発生しません。

[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct MessageHeader
{
    public uint  Magic;        // 4 bytes
    public ushort Version;    // 2 bytes
    public ushort PayloadLen; // 2 bytes
    public fixed byte Id[16]; // 16 bytes GUID inline
}

unsafe ReadOnlySpan<byte> SerializeHeader(MessageHeader hdr)
{
    // Cast struct directly to bytes — zero copy
    return new ReadOnlySpan<byte>(&hdr, sizeof(MessageHeader));
}

unsafe MessageHeader ParseHeader(ReadOnlySpan<byte> buf)
{
    fixed (byte* p = buf)
        return *(MessageHeader*)p; // reinterpret cast
}

理解度チェック

マネージドオブジェクトのアドレスを取得するときに、fixedステートメントが必要なのはなぜですか?

振り返り:unsafeコード、ポインター、固定長バッファー

主なポイント:

  • unsafeキーワードで生ポインターを使用できるようになります。プロジェクトでAllowUnsafeBlocksが必要です
  • fixedステートメントはマネージドオブジェクトをピン留めし、GCによる移動を防ぎます
  • ポインター演算では、バイト数ではなく要素のサイズ分だけ進みます
  • stackallocはスタック上にメモリを確保するため、GCへの負荷がなく、メソッド終了時に解放されます
  • 固定長バッファー(fixed byte Payload[N])は、構造体内に配列をインラインで埋め込みます
  • ほとんどの場面では、Span<T>、MemoryMarshal、Unsafeクラスを優先します

よくある質問

「安全でないコード、ポインター、固定バッファー」レッスンは無料ですか?

はい。「安全でないコード、ポインター、固定バッファー」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。

「安全でないコード、ポインター、固定バッファー」で何を学びますか?

unsafe キーワードを使い、ポインターを操作し、fixed で管理メモリを固定し、構造体内の固定長バッファーにアクセスします。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C# Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「安全でないコード、ポインター、固定バッファー」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC# Academyレッスンでコードを書いて実行できますか?

はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. P/Invokeの基礎
  2. LibraryImportとソース生成P/Invoke
  3. 安全でないコード、ポインター、固定バッファー
  4. COM 相互運用とランタイム呼び出し可能ラッパー
← C# Academyに戻る