bucket-sort logo bucket-sort

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

  • Posts
  • About
  • Contact
  1. Home
  2. All Posts
  3. [C#] ポインター型(Pointer Types)入門 ① — unsafe コードの基本と * / & 演算子

[C#] ポインター型(Pointer Types)入門 ① — unsafe コードの基本と * / & 演算子

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

C++ の経験があれば、ポインターそのものは馴染み深い概念のはずです。C# では通常 .NET ランタイムのガベージコレクター(GC)がメモリを管理しますが、unsafe キーワードを使うことで C/C++ 同様にポインターを操作できます。

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

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

なぜ C# でポインターが必要か?

C# はマネージド言語です。.NET ランタイムが GC を通じてメモリの確保と解放を自動的に行うため、C/C++ のように malloc/free を手動で呼ぶ必要はありません。通常のアプリケーション開発では、ポインターを使う機会はほとんどありません。

では、なぜポインターが用意されているのでしょうか。主に次の 2 つの場面で必要になります。

(1) .NET ランタイムの管理外でメモリを直接操作してパフォーマンスを最大化したい

GC が管理するヒープを介さずにメモリを直接操作することで、特定の処理を大幅に高速化できます。典型的な例は画像処理です。ビットマップ画像を 1 ピクセルずつ処理する場合、安全な API(GetPixel / SetPixel)は 1 回ごとにオーバーヘッドが発生しますが、ポインターで生のバイト列に直接アクセスすれば数十倍速くなることがあります。

実際のコード例は ③ 実践例:ビットマップ画像処理 で紹介します。

(2) ポインター型を引数に要求する C ベースの DLL や COM サーバーのメソッドを呼び出したい

Windows API や既存の C/C++ ライブラリは、多くの場合ポインターを引数として受け取ります。P/Invoke(Platform Invocation Services)でそのような関数を呼び出すとき、C# 側でポインターを扱う必要が生じます。

ただし、この場合は System.IntPtr 型や System.Runtime.InteropServices.Marshal クラスを活用すれば、unsafe コードを使わずに済むことも多くあります。両方のアプローチの比較は ④ 実践例:C++ DLL との相互運用 で紹介します。

マネージドコードを基本に

C# の設計思想は「安全なコードを書きやすく」することにあります。ポインターは強力ですが、GC との干渉、メモリ破壊のリスク、デバッグの難しさを伴います。Span<T>、ref 引数、Memory<T> など、多くのケースでポインターなしに同等のパフォーマンスを達成できる手段が .NET に用意されています。ポインターはどうしても必要な場面に絞って使うのが原則です。

この記事(①)の内容

  1. ポインター専用の演算子とキーワード一覧
  2. unsafe コードの有効化
  3. unsafe キーワード
  4. * と & 演算子の使い方
  5. unsafe なスワップ関数と安全版との比較

ポインター専用の演算子とキーワード一覧

C# でポインターを扱うために用意された演算子とキーワードを整理します。

記号 / キーワード 意味
* 間接参照(デリファレンス)またはポインター型の宣言
& アドレス取得(変数のアドレスをポインターに格納)
-> ポインター経由でフィールドやプロパティにアクセス
[] ポインターの添字アクセス(*(ptr + i) と等価)
++, -- ポインターのインクリメント・デクリメント(型サイズ単位で移動)
+, - ポインター演算(バイト数ではなく要素数単位)
==, !=, <, >, <=, >= ポインターのアドレス比較
stackalloc スタック上にメモリを確保する
fixed GC によるオブジェクトの移動を一時的に禁止してポインターを固定する

C++ を知っている方には見慣れた記号ばかりですが、C# では unsafe ブロックの中でのみ使用できます。

unsafe コードの有効化

Visual Studio での設定

C# プロジェクトで unsafe コードを使うには、プロジェクトの設定でオプションを有効にする必要があります。

Visual Studio の場合:

  1. ソリューションエクスプローラーでプロジェクトを右クリック
  2. 「プロパティ」 を開く
  3. 「ビルド」 タブを選択
  4. 「アンセーフ コードを許可する(Allow unsafe code)」 にチェックを入れる

.csproj ファイルでの設定

.csproj ファイルを直接編集する場合は、<AllowUnsafeBlocks> を true にします。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
</Project>

この設定なしで unsafe キーワードを使うとコンパイルエラーになります。

unsafe キーワード

unsafe はメソッド、クラス、ブロックの 3 つの単位で指定できます。

メソッド全体を unsafe にする

// メソッド全体を unsafe にする
static unsafe void ShowAddresses()
{
    int x = 10;
    int* pX = &x;
    Console.WriteLine($"x の値: {x}");
    Console.WriteLine($"x のアドレス: 0x{(long)pX:X}");
}

ブロック単位で unsafe にする

static void Main()
{
    int value = 42;

    // このブロック内だけ unsafe
    unsafe
    {
        int* p = &value;
        Console.WriteLine($"value = {*p}"); // 42
    }

    // ここでは p は使えない
}

クラス全体を unsafe にする

// クラス全体を unsafe にする(ポインター操作が多い場合)
unsafe class LowLevelProcessor
{
    public void Process(int* data, int length)
    {
        for (int i = 0; i < length; i++)
            data[i] *= 2;
    }
}

ポインター操作が必要な範囲を最小限に絞るのが安全です。クラス全体を unsafe にするより、メソッドやブロック単位で指定するほうが推奨されます。

* と & 演算子の使い方

C++ と基本的に同じです。& でアドレスを取得し、* でデリファレンスします。

unsafe static void PointerBasics()
{
    // int 変数の宣言
    int value = 100;

    // & でアドレスを取得し、int* 型のポインターに格納
    int* p = &value;

    Console.WriteLine($"value  = {value}");       // 100
    Console.WriteLine($"*p     = {*p}");           // 100
    Console.WriteLine($"&value = 0x{(long)p:X}"); // アドレス(実行ごとに変わる)

    // * で値を書き換える
    *p = 999;
    Console.WriteLine($"value  = {value}");        // 999(p 経由で変更された)
}

ポインターへのポインター

C++ と同様に、ポインターへのポインターも扱えます。

unsafe static void DoublePointer()
{
    int x = 5;
    int* p = &x;
    int** pp = &p;

    Console.WriteLine($"x    = {x}");    // 5
    Console.WriteLine($"*p   = {*p}");   // 5
    Console.WriteLine($"**pp = {**pp}"); // 5

    **pp = 77;
    Console.WriteLine($"x    = {x}");   // 77
}

ポインター演算

ポインターの ++ / -- や + / - は、C++ 同様に型のサイズ単位で移動します。

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

    fixed (int* start = numbers) // fixed については後編で詳しく説明
    {
        int* p = start;

        for (int i = 0; i < numbers.Length; i++)
        {
            Console.WriteLine($"*(start + {i}) = {*(start + i)}");
            // 添字アクセスも使える: start[i]
        }
    }
}
// *(start + 0) = 10
// *(start + 1) = 20
// *(start + 2) = 30
// *(start + 3) = 40
// *(start + 4) = 50

アドレス比較

ポインター同士のアドレスを ==、!=、<、> などで比較できます。

unsafe static void PointerComparison()
{
    int[] arr = { 1, 2, 3 };

    fixed (int* p = arr)
    {
        int* first = p;
        int* last  = p + arr.Length - 1;

        Console.WriteLine(first < last); // True
        Console.WriteLine(first == p);   // True
    }
}

unsafe なスワップ関数と安全版との比較

ポインターを使った swap は C のコードと全く同じ構造です。

unsafe 版

unsafe static void SwapUnsafe(int* a, int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
int x = 10, y = 20;

unsafe
{
    SwapUnsafe(&x, &y);
}

Console.WriteLine($"x = {x}, y = {y}"); // x = 20, y = 10

安全版(ref を使う)

同じことは C# では ref キーワードで unsafe なしに実現できます。

static void SwapSafe(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}
int x = 10, y = 20;
SwapSafe(ref x, ref y);
Console.WriteLine($"x = {x}, y = {y}"); // x = 20, y = 10

ref を使うほうが安全で、GC との競合も起きません。ポインターは ref や Span<T> で代替できないケースに限定するのが C# での推奨スタイルです。

まとめ

トピック ポイント
有効化 .csproj の <AllowUnsafeBlocks>true</AllowUnsafeBlocks> が必要
unsafe キーワード メソッド・ブロック・クラスの単位で指定できる
* と & C/C++ と同じ。& でアドレス取得、* でデリファレンス
ポインター演算 型サイズ単位で移動。添字アクセス ptr[i] も使える
unsafe スワップ C と同じ構造で書ける。C# では ref で代替可能

② ポインター型入門 では -> 演算子によるフィールドアクセス、スタックへのメモリ確保(stackalloc)、GC によるオブジェクト移動の抑制(fixed)、型のサイズ確認(sizeof)を解説します。

C# .NET Unsafe ポインター Pointer メモリ管理
← [C#] 匿名型(Anonymous Types)の使い方 [C#] ポインター型(Pointer Types)② — -> 演算子 / stackalloc / fixed / sizeof →

Related Posts

  • [C#] ポインター実践例② — P/Invoke を使った C++ DLL との相互運用 Jun 12, 2026
  • [C#] ポインター実践例① — unsafe コードによるビットマップ画像処理の高速化 Jun 11, 2026
  • [C#] ポインター型(Pointer Types)② — -> 演算子 / stackalloc / fixed / sizeof Jun 10, 2026
  • [C#] Disposable Objects — IDisposable / Dispose() と using 構文 May 12, 2026

Table of Contents

  • なぜ C# でポインターが必要か?
    • (1) .NET ランタイムの管理外でメモリを直接操作してパフォーマンスを最大化したい
    • (2) ポインター型を引数に要求する C ベースの DLL や COM サーバーのメソッドを呼び出したい
    • マネージドコードを基本に
  • この記事(①)の内容
  • ポインター専用の演算子とキーワード一覧
  • unsafe コードの有効化
    • Visual Studio での設定
    • .csproj ファイルでの設定
  • unsafe キーワード
    • メソッド全体を unsafe にする
    • ブロック単位で unsafe にする
    • クラス全体を unsafe にする
  • * と & 演算子の使い方
    • ポインターへのポインター
    • ポインター演算
    • アドレス比較
  • unsafe なスワップ関数と安全版との比較
    • unsafe 版
    • 安全版(ref を使う)
  • まとめ

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.