ポインター

# 配列アクセス用のポインタ

この例は、C# 配列への C のようなアクセスにポインターを使用する方法を示しています。

unsafe
{
    var buffer = new int[1024];
    fixed (int* p = &buffer[0])
    {
        for (var i = 0; i < buffer.Length; i++)
        {
            *(p + i) = i;
        }
    }
}

unsafe 通常の方法で C# 配列にアクセスするときに通常発行される境界チェックは、ポインター アクセスでは発行されないため、キーワードが必要です。

fixed キーワードは、例外セーフな方法でオブジェクトを固定する命令を発行するように C# コンパイラに指示します。ガベージ コレクターがメモリ内の配列を移動しないようにするために固定が必要です。移動すると、配列内を指しているポインターが無効になるためです。

# ポインター演算

ポインターでの加算と減算は、整数とは異なる動作をします。ポインターがインクリメントまたはデクリメントされると、ポインターが指すアドレスは参照先の型のサイズだけ増減されます。

たとえば、タイプ int (System.Int32 の別名 ) のサイズは 4 です。int の場合 アドレス 0 に格納でき、その後の int アドレス 4 などに格納できます。コード内:

var ptr = (int*)IntPtr.Zero;
Console.WriteLine(new IntPtr(ptr)); // prints 0
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 4
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 8

同様に、タイプ long (System.Int64 の別名 ) のサイズは 8 です。long の場合 アドレス 0 に格納でき、その後の long アドレス 8 などに格納できます。コード内:

var ptr = (long*)IntPtr.Zero;
Console.WriteLine(new IntPtr(ptr)); // prints 0
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 8
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 16

タイプ void は特別で void ポインターも特殊であり、型が不明または重要でない場合にキャッチオール ポインターとして使用されます。サイズにとらわれない性質のため、void ポインタはインクリメントまたはデクリメントできません:

var ptr = (void*)IntPtr.Zero;
Console.WriteLine(new IntPtr(ptr));
ptr++; // compile-time error
Console.WriteLine(new IntPtr(ptr));
ptr++; // compile-time error
Console.WriteLine(new IntPtr(ptr));

# アスタリスクは型の一部です

C および C++ では、ポインター変数の宣言のアスタリスクは式の一部です。 宣言されています。 C# では、宣言のアスタリスクは型の一部です .

C、C++、および C# では、次のスニペットは int を宣言します。 ポインタ:

int* a;

C および C++ では、次のスニペットは int を宣言します。 ポインターと int 変数。 C# では、2 つの int を宣言します。 ポインタ:

int* a, b; 

C および C++ では、次のスニペットは 2 つの int を宣言します。 ポインター。 C# では無効です:

int *a, *b;

# ボイド*

C# は C および C++ から void* の使用法を継承します タイプやサイズにとらわれないポインターとして。

void* ptr;

void* には任意のポインタ型を割り当てることができます 暗黙の変換を使用:

int* p1 = (int*)IntPtr.Zero;
void* ptr = p1;

逆の場合は、明示的な変換が必要です:

int* p1 = (int*)IntPtr.Zero;
void* ptr = p1;
int* p2 = (int*)ptr;

# メンバー アクセス using ->

C# は、C および C++ からシンボル -> の使用法を継承します。 型付きポインターを介してインスタンスのメンバーにアクセスする手段として。

次の構造体を検討してください:

struct Vector2
{
    public int X;
    public int Y;
}

これは -> の使用例です そのメンバーにアクセスするには:

Vector2 v;
v.X = 5;
v.Y = 10;

Vector2* ptr = &v;
int x = ptr->X;
int y = ptr->Y;
string s = ptr->ToString();

Console.WriteLine(x); // prints 5
Console.WriteLine(y); // prints 10
Console.WriteLine(s); // prints Vector2

# ジェネリック ポインター

ポインタをサポートするために型が満たさなければならない基準 (備考を参照) ) は、一般的な制約で表すことはできません。したがって、ジェネリック型パラメーターを介して提供される型へのポインターを宣言しようとすると失敗します。

void P<T>(T obj) 
    where T : struct
{
    T* ptr = &obj; // compile-time error
}

# コメント

# ポインタと unsafe

その性質上、ポインターは検証不能なコードを生成します。したがって、任意のポインター型を使用するには unsafe が必要です

タイプ System.IntPtr void* の安全なラッパーです . void* のより便利な代替手段として意図されています。 目の前のタスクを実行するために安全でないコンテキストが必要でない場合。

# 未定義の動作

C や C++ と同様に、ポインタの不適切な使用は未定義の動作を引き起こす可能性があり、メモリの破損や意図しないコードの実行などの副作用が生じる可能性があります。ほとんどのポインター操作は検証不可能な性質を持っているため、ポインターを正しく使用することは完全にプログラマーの責任です。

# ポインタをサポートする型

C や C++ とは異なり、すべての C# 型に対応するポインター型があるわけではありません。タイプ T 次の基準の両方が当てはまる場合、対応するポインター型を持つことができます:

  • T 構造体型またはポインター型です。
  • T これらの基準の両方を再帰的に満たすメンバーのみを含みます。