bucket-sort logo bucket-sort

プログラミングとインフラエンジニアリングの覚え書き

  • Posts
  • About
  • Contact
  1. Home
  2. All Posts
  3. [C#] ポインター型(Pointer Types)② — -> 演算子 / stackalloc / fixed / sizeof

[C#] ポインター型(Pointer Types)② — -> 演算子 / stackalloc / fixed / sizeof

Jun 10, 2026 C# , .NET bucket-sort

このシリーズは全 4 記事で構成されています。

  • ① なぜポインターが必要か / unsafe の基本 / * と & 演算子
  • ② 本記事 — -> 演算子 / stackalloc / fixed / sizeof
  • ③ 実践例:ビットマップ画像処理
  • ④ 実践例:C++ DLL との相互運用

① では、unsafe キーワードの有効化、* と & 演算子の基本、ポインター演算を説明しました。

この記事では次を説明します。

  1. -> 演算子によるフィールドアクセス
  2. stackalloc キーワード
  3. fixed キーワードによるピン止め
  4. sizeof キーワード

-> 演算子によるフィールドアクセス

構造体のポインターがあるとき、フィールドにアクセスするには C++ と同様に -> 演算子が使えます。ptr->Field は (*ptr).Field と等価です。

まず unsafe ブロック内で使える構造体を定義します(参照型である class はポインターで直接扱えません。struct を使います)。

struct Point
{
    public int X;
    public int Y;

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public override string ToString() => $"({X}, {Y})";
}
unsafe static void UseArrowOperator()
{
    Point pt = new Point(10, 20);
    Point* p = &pt;

    // -> でフィールドにアクセス
    Console.WriteLine($"X = {p->X}"); // X = 10
    Console.WriteLine($"Y = {p->Y}"); // Y = 20

    // -> で値を書き換える
    p->X = 99;
    Console.WriteLine(pt); // (99, 20)

    // (*p).X と書いても同じ
    (*p).Y = 77;
    Console.WriteLine(pt); // (99, 77)
}

構造体のネスト

ネストした構造体も連続して -> でたどれます。

struct Rectangle
{
    public Point TopLeft;
    public Point BottomRight;

    public override string ToString()
        => $"[{TopLeft} - {BottomRight}]";
}
unsafe static void NestedArrow()
{
    Rectangle rect = new Rectangle
    {
        TopLeft     = new Point(0, 0),
        BottomRight = new Point(100, 50)
    };

    Rectangle* rp = ▭

    Console.WriteLine(rp->TopLeft.X);     // 0
    Console.WriteLine(rp->BottomRight.Y); // 50

    // ネストした構造体へのポインター
    Point* pp = &rp->TopLeft;
    pp->X = 10;

    Console.WriteLine(rect); // [(10, 0) - (100, 50)]
}

stackalloc キーワード

stackalloc を使うと、ヒープではなくスタック上にメモリを確保できます。スタック確保はヒープよりも高速で、GC の対象にならないため fixed も不要です。確保されたメモリはメソッドのスタックフレームが解放されると同時に消えます。

unsafe static void UseStackalloc()
{
    // スタックに int 5 要素分のメモリを確保
    int* buffer = stackalloc int[5];

    for (int i = 0; i < 5; i++)
        buffer[i] = i * 10;

    for (int i = 0; i < 5; i++)
        Console.Write($"{buffer[i]} "); // 0 10 20 30 40
    Console.WriteLine();
}

Span<T> との組み合わせ(C# 7.2 以降)

C# 7.2 以降では Span<T> と組み合わせると、unsafe なしでも stackalloc が使えます。

// unsafe 不要(Span<T> で受け取る場合)
static void UseStackallocWithSpan()
{
    Span<int> buffer = stackalloc int[5];

    for (int i = 0; i < 5; i++)
        buffer[i] = (i + 1) * 100;

    foreach (var v in buffer)
        Console.Write($"{v} "); // 100 200 300 400 500
    Console.WriteLine();
}

Span<T> で受け取る場合は unsafe ブロックが不要になり、インデックスの範囲チェックも入るため安全です。スタック確保のパフォーマンスが欲しい場合は、この形が推奨されます。

ただし、スタックは容量が限られているため、大きなバッファには使わないことが重要です。目安として数百バイト〜数キロバイト程度が実用的な範囲です。

fixed キーワードによるピン止め

C# の GC はメモリの断片化を防ぐためにオブジェクトをヒープ上で移動することがあります。管理対象のオブジェクト(managed object)へのポインターを取得すると、GC がオブジェクトを移動した瞬間にポインターが無効になります。

fixed ステートメントは GC によるオブジェクトの移動を一時的に禁止し、安全にポインターを固定(ピン止め)します。

unsafe static void UseFixed()
{
    int[] numbers = { 10, 20, 30, 40, 50 };

    // numbers 配列を GC が移動しないようにピン止めしてポインターを取得
    fixed (int* p = numbers)
    {
        for (int i = 0; i < numbers.Length; i++)
        {
            Console.WriteLine($"p[{i}] = {p[i]}");
        }

        // ポインター演算でもアクセスできる
        int* end = p + numbers.Length;
        for (int* ptr = p; ptr < end; ptr++)
            Console.Write($"{*ptr} "); // 10 20 30 40 50
        Console.WriteLine();
    }
    // fixed ブロックを抜けるとピン止めが解除される
}

文字列のピン止め

文字列(string)も fixed でピン止めして char* ポインターを取得できます。

unsafe static void FixedString()
{
    string text = "Hello, C#!";

    fixed (char* p = text)
    {
        char* ptr = p;
        while (*ptr != '\0')
        {
            Console.Write(*ptr);
            ptr++;
        }
        Console.WriteLine();
    }
    // Hello, C#!
}

構造体フィールドのピン止め

クラスのフィールドに対しても fixed が使えます。ただし、フィールドが固定サイズバッファ(fixed フィールド)である必要があります。

unsafe struct Pixel
{
    public fixed byte Channels[4]; // R, G, B, A の 4 バイト固定バッファ
}
unsafe static void UseFixedBuffer()
{
    Pixel pixel;
    pixel.Channels[0] = 255; // R
    pixel.Channels[1] = 128; // G
    pixel.Channels[2] = 0;   // B
    pixel.Channels[3] = 255; // A

    byte* p = pixel.Channels;
    Console.WriteLine($"R={p[0]}, G={p[1]}, B={p[2]}, A={p[3]}");
    // R=255, G=128, B=0, A=255
}

fixed フィールドを持つ構造体は、インラインで固定サイズの配列を持てるため、ネイティブの相互運用(P/Invoke など)でよく使われます。

fixed を使うべき場面

場面 理由
マネージド配列を C API に渡す GC が移動するとアドレスが無効になるため
文字列の文字を高速に走査する char* で直接アクセスするほうが速い場合がある
P/Invoke でバッファを渡す ネイティブコードはアドレスが変わると困る

sizeof キーワード

sizeof は型のバイトサイズをコンパイル時に取得します。組み込み型(int、double など)は unsafe なしでも使えます。

// unsafe 不要(組み込み型)
Console.WriteLine(sizeof(int));    // 4
Console.WriteLine(sizeof(long));   // 8
Console.WriteLine(sizeof(double)); // 8
Console.WriteLine(sizeof(char));   // 2
Console.WriteLine(sizeof(bool));   // 1
Console.WriteLine(sizeof(byte));   // 1

ユーザー定義の構造体のサイズを取得するには unsafe が必要です。

struct Point3D
{
    public float X, Y, Z;
}

unsafe static void ShowStructSize()
{
    Console.WriteLine(sizeof(Point3D)); // 12(float 4 バイト × 3)
}

Marshal.SizeOf との違い

sizeof と似た用途で Marshal.SizeOf がありますが、目的が異なります。

sizeof Marshal.SizeOf
計測対象 .NET 上の型サイズ アンマネージドメモリ上のサイズ
unsafe 要否 組み込み型は不要、構造体は必要 不要
パディング .NET のアラインメントに従う アンマネージドの規則に従う
主な用途 ポインター演算、メモリ計算 P/Invoke、マーシャリング
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct PackedPoint
{
    public byte  X; // 1 バイト
    public int   Y; // 4 バイト
}

unsafe static void CompareSizes()
{
    // .NET のアラインメント: X(1) + パディング(3) + Y(4) = 8
    Console.WriteLine(sizeof(PackedPoint));            // 8
    // Pack=1 指定のアンマネージドサイズ: 1 + 4 = 5
    Console.WriteLine(Marshal.SizeOf<PackedPoint>());  // 5
}

まとめ

トピック ポイント
-> 演算子 構造体ポインター経由のフィールドアクセス。(*p).Field と等価
stackalloc スタックにメモリ確保。GC 対象外で高速。Span<T> と組み合わせると unsafe 不要
fixed GC によるオブジェクト移動を一時停止してポインターを安全に使う
sizeof 組み込み型はどこでも使用可。ユーザー定義型は unsafe が必要

C++ 経験者にとって概念自体はなじみ深いはずですが、C# では GC との共存が常に意識すべき点です。fixed は GC のスループットを一時的に下げる可能性があるため、使用範囲はできる限り狭く保つのが望ましいです。多くの場合、Span<T> や ref 構造体を使えば unsafe なしに同等のパフォーマンスが得られます。

次は実践編です。③ ビットマップ画像処理 ではポインターで画像処理を高速化する例、④ C++ DLL との相互運用 では P/Invoke でポインターを渡す例を紹介します。

C# .NET Unsafe ポインター Pointer Stackalloc Fixed Sizeof
← [C#] ポインター型(Pointer Types)入門 ① — unsafe コードの基本と * / & 演算子 [C#] ポインター実践例① — unsafe コードによるビットマップ画像処理の高速化 →

Related Posts

  • [C#] ポインター実践例② — P/Invoke を使った C++ DLL との相互運用 Jun 12, 2026
  • [C#] ポインター実践例① — unsafe コードによるビットマップ画像処理の高速化 Jun 11, 2026
  • [C#] ポインター型(Pointer Types)入門 ① — unsafe コードの基本と * / & 演算子 Jun 9, 2026
  • [C#] プラグインで拡張できるアプリケーションを設計する Jul 9, 2026

Table of Contents

  • -> 演算子によるフィールドアクセス
    • 構造体のネスト
  • stackalloc キーワード
    • Span<T> との組み合わせ(C# 7.2 以降)
  • fixed キーワードによるピン止め
    • 文字列のピン止め
    • 構造体フィールドのピン止め
    • fixed を使うべき場面
  • sizeof キーワード
    • Marshal.SizeOf との違い
  • まとめ

Recent Posts

  • [C#] プラグインで拡張できるアプリケーションを設計する Jul 9, 2026
  • [C#] 属性でコードに意味を与える Jul 8, 2026
  • [C#] 外部アセンブリを読み込み実行時に型を呼び出す Jul 7, 2026
  • [C#] リフレクションで型の中身を調べる Jul 6, 2026
  • [C#] 型メタデータとリフレクションの基礎を理解する Jul 5, 2026

Categories

  • C#120
  • .NET119
  • AWS27
  • Laravel16
  • Linux15
  • MySQL9
  • Apache8
  • PHP8
  • DynamoDB6
  • セキュリティ6
  • Nginx5
  • WordPress4
  • インフラ4
  • Hugo3
  • .NET Framework1
  • Aurora1
  • Diagnostics1
  • Filament1
  • Git1
  • SQS1

Tags

  • C#
  • .NET
  • AWS
  • Laravel
  • コレクション
  • PHP
  • セキュリティ
  • MySQL
  • Linux
  • パフォーマンス
  • Apache
  • System.Collections.Generic
  • デリゲート
  • Code Snippet
  • DynamoDB
  • LINQ
  • NoSQL
  • PHP-FPM
  • RDS
  • System.Collections
  • Windows
  • メモリ管理
  • リフレクション
  • DoS
  • Nginx
  • WordPress
  • メタデータ
  • ラムダ式
  • 監視
  • 設計
  • Amazon Linux 2023
  • Delegate
  • Docker
  • IDisposable
  • Ipset
  • Iptables
  • LINQ to Objects
  • OPCache
  • Pointer
  • Reflection
  • System.Collections.Specialized
  • Unsafe
  • Webサーバー
  • インターフェース
  • オブジェクト指向
  • クラス設計
  • デザインパターン
  • パターンマッチング
  • ポインター
  • 継承
Powered by Hugo & Explore Theme.