ポインター関連演算子 - 変数のアドレスの取得、メモリ位置の逆参照、メモリ位置へのアクセス

ポインター演算子を使うと、変数のアドレスの取得 (&)、ポインターの逆参照 (*)、ポインター値の比較、ポインターと整数の加算と減算を行うことができます。

ポインターを操作するには、次の演算子を使います。

ポインター型については、「ポインター型」をご覧ください。

注意

ポインターに関するすべての操作には、unsafe コンテキストが必要です。 unsafe ブロックを含むコードは、AllowUnsafeBlocks コンパイラ オプションを使ってコンパイルする必要があります。

アドレス取得演算子 &

単項の & 演算子からは、そのオペランドのアドレスが返されます。

unsafe
{
    int number = 27;
    int* pointerToNumber = &number;

    Console.WriteLine($"Value of the variable: {number}");
    Console.WriteLine($"Address of the variable: {(long)pointerToNumber:X}");
}
// Output is similar to:
// Value of the variable: 27
// Address of the variable: 6C1457DBD4

& 演算子のオペランドは、固定変数である必要があります。 "固定" 変数とは、ガベージ コレクターの操作によって影響を受けない記憶域の場所に存在する変数です。 前の例では、ローカル変数 number はスタックに存在するので固定変数です。 ガベージ コレクターによって影響を受ける可能性がある (たとえば再配置) 記憶域の場所にある変数は、"移動可能" 変数と呼ばれます。 オブジェクト フィールドや配列の要素は、移動可能変数の例です。 fixed ステートメントで移動可能変数を "固定" または "ピン留め" した場合は、移動可能変数のアドレスを取得できます。 取得したアドレスは、fixed ステートメントのブロック内でのみ有効です。 fixed ステートメントと & 演算子の使い方の例を次に示します。

unsafe
{
    byte[] bytes = { 1, 2, 3 };
    fixed (byte* pointerToFirst = &bytes[0])
    {
        // The address stored in pointerToFirst
        // is valid only inside this fixed statement block.
    }
}

定数または値のアドレスを取得できません。

固定変数と移動可能変数について詳しくは、「C# 言語仕様」の「Fixed and moveable variables (固定変数と移動可能変数)」セクションをご覧ください。

2 項 & 演算子では、ブール型オペランドの論理 AND または整数オペランドのビットごとの論理 AND が計算されます。

ポインター間接参照演算子 *

単項ポインター間接参照演算子 * では、オペランドが指し示す変数が取得されます。 逆参照演算子とも呼ばれます。 * 演算子のオペランドは、ポインター型である必要があります。

unsafe
{
    char letter = 'A';
    char* pointerToLetter = &letter;
    Console.WriteLine($"Value of the `letter` variable: {letter}");
    Console.WriteLine($"Address of the `letter` variable: {(long)pointerToLetter:X}");

    *pointerToLetter = 'Z';
    Console.WriteLine($"Value of the `letter` variable after update: {letter}");
}
// Output is similar to:
// Value of the `letter` variable: A
// Address of the `letter` variable: DCB977DDF4
// Value of the `letter` variable after update: Z

void* 型の式に * 演算子を適用することはできません。

2 項 * 演算子では、数値オペランドのが計算されます。

ポインター メンバー アクセス演算子 ->

-> 演算子では、ポインターの間接参照メンバー アクセスが組み合わされます。 つまり、xT* 型のポインターで、yT 型のアクセス可能なメンバーである場合に、次のような形式の式を考えます

x->y

上記の式は、次の式と同じです。

(*x).y

-> 演算子の使用例を次に示します。

public struct Coords
{
    public int X;
    public int Y;
    public override string ToString() => $"({X}, {Y})";
}

public class PointerMemberAccessExample
{
    public static unsafe void Main()
    {
        Coords coords;
        Coords* p = &coords;
        p->X = 3;
        p->Y = 4;
        Console.WriteLine(p->ToString());  // output: (3, 4)
    }
}

void* 型の式に -> 演算子を適用することはできません。

ポインター要素アクセス演算子 []

ポインター型の式 p の場合、p[n] の形式のポインター要素アクセスは、*(p + n) と評価されます。n は、intuintlong、または ulong に暗黙的に変換できる型でなければなりません。 ポインターでの + 演算子の動作については、「ポインターに対する整数値の加算または減算」セクションをご覧ください。

次の例では、ポインターと [] 演算子による配列要素へのアクセス方法を示します。

unsafe
{
    char* pointerToChars = stackalloc char[123];

    for (int i = 65; i < 123; i++)
    {
        pointerToChars[i] = (char)i;
    }

    Console.Write("Uppercase letters: ");
    for (int i = 65; i < 91; i++)
    {
        Console.Write(pointerToChars[i]);
    }
}
// Output:
// Uppercase letters: ABCDEFGHIJKLMNOPQRSTUVWXYZ

前の例では、stackallocによってスタックにメモリ ブロックが割り当てられています。

注意

ポインター要素アクセス演算子では、範囲外のエラーはチェックされません。

void* 型の式でポインター要素アクセスに [] を使うことはできません。

配列要素またはインデクサー アクセスには [] 演算子を使用することもできます。

ポインター算術演算子

ポインターで次の算術演算を実行できます。

  • ポインターに整数値を加算する、またはポインターから整数値を減算する
  • 2 個のポインターを減算する
  • ポインターをインクリメントまたはデクリメントする

void* 型のポインターでこれらの演算を実行することはできません。

数値型でサポートされている算術演算については、「算術演算子」をご覧ください。

ポインターへの整数値の加算またはポインターからの整数値の減算

T* 型のポインター p と、intuintlong、または ulong に暗黙的に変換できる型の式n の場合、加算と減算は次のように定義されます。

  • p + n および n + p では、どちらの場合も、p によって与えられるアドレスに n * sizeof(T) を加算した結果である、T* 型のポインターが生成されます。
  • p - n では、p によって与えられるアドレスから n * sizeof(T) を減算した結果である、T* 型のポインターが生成されます。

sizeof 演算子では、型のサイズ (バイト単位) が取得されます。

次の例では、ポインターでの + 演算子の使用方法を示します。

unsafe
{
    const int Count = 3;
    int[] numbers = new int[Count] { 10, 20, 30 };
    fixed (int* pointerToFirst = &numbers[0])
    {
        int* pointerToLast = pointerToFirst + (Count - 1);

        Console.WriteLine($"Value {*pointerToFirst} at address {(long)pointerToFirst}");
        Console.WriteLine($"Value {*pointerToLast} at address {(long)pointerToLast}");
    }
}
// Output is similar to:
// Value 10 at address 1818345918136
// Value 30 at address 1818345918144

ポインターの減算

T* 型の 2 つのポインター p1p2 の場合、式 p1 - p2 では、p1p2 によって指定されるアドレスの間の差を sizeof(T) によって除算した値が生成されます。 結果の型は long です。 つまり、p1 - p2((long)(p1) - (long)(p2)) / sizeof(T) として計算されます。

ポインターの減算の例を次に示します。

unsafe
{
    int* numbers = stackalloc int[] { 0, 1, 2, 3, 4, 5 };
    int* p1 = &numbers[1];
    int* p2 = &numbers[5];
    Console.WriteLine(p2 - p1);  // output: 4
}

ポインターのインクリメントとデクリメント

++ インクリメント演算子では、ポインター オペランドに 1 が加算されます。 -- デクリメント演算子では、ポインター オペランドから 1 が減算されます。

どちらの演算子も、後置 (p++ および p--) と前置 (++p および --p) の 2 つの形式でサポートされます。 p++ および p-- の結果は、演算の "" の p の値です。 ++p および --p の結果は、演算の "" の p の値です。

次の例では、後置と前置両方のインクリメント演算子の動作を示します。

unsafe
{
    int* numbers = stackalloc int[] { 0, 1, 2 };
    int* p1 = &numbers[0];
    int* p2 = p1;
    Console.WriteLine($"Before operation: p1 - {(long)p1}, p2 - {(long)p2}");
    Console.WriteLine($"Postfix increment of p1: {(long)(p1++)}");
    Console.WriteLine($"Prefix increment of p2: {(long)(++p2)}");
    Console.WriteLine($"After operation: p1 - {(long)p1}, p2 - {(long)p2}");
}
// Output is similar to
// Before operation: p1 - 816489946512, p2 - 816489946512
// Postfix increment of p1: 816489946512
// Prefix increment of p2: 816489946516
// After operation: p1 - 816489946516, p2 - 816489946516

ポインター比較演算子

==!=<><=>= 演算子を使って、void* を含む任意のポインター型のオペランドを比較できます。 これらの演算子では、2 つのオペランドによって指定されるアドレスが、符号なし整数のように比較されます。

他の型のオペランドに対するこれらの演算子の動作については、「等値演算子」および「比較演算子」をご覧ください。

演算子の優先順位

次のポインター関連演算子の一覧は、優先度が高い順に並べられています。

  • 後置インクリメント x++ およびデクリメント x-- 演算子、-> および [] 演算子
  • 前置インクリメント ++x およびデクリメント --x 演算子、& および * 演算子
  • 加法 + および - 演算子
  • 比較 <><=>= 演算子
  • 等値 == および != 演算子

演算子の優先順位によって定められた評価の順序を変更するには、かっこ () を使用します。

優先度順に並べられた C# 演算子の完全な一覧については、C# 演算子に関する記事の「演算子の優先順位」セクションを参照してください。

演算子のオーバーロード可/不可

ユーザー定義型では、ポインター関連演算子 &*->[] をオーバーロードできません。

C# 言語仕様

詳細については、「C# 言語仕様」の次のセクションを参照してください。

関連項目