ポインター関連演算子 - 変数のアドレスの取得、メモリ位置の逆参照、メモリ位置へのアクセス
ポインター演算子を使うと、変数のアドレスの取得 (&
)、ポインターの逆参照 (*
)、ポインター値の比較、ポインターと整数の加算と減算を行うことができます。
ポインターを操作するには、次の演算子を使います。
- 単項
&
(アドレス取得) 演算子: 変数のアドレスを取得します - 単項
*
(ポインター間接参照) 演算子: ポインターが指す位置にある変数を取得します ->
(メンバー アクセス) および[]
(要素アクセス) 演算子- 算術演算子
+
、-
、++
、--
- 比較演算子
==
、!=
、<
、>
、<=
、>=
ポインター型については、「ポインター型」をご覧ください。
注意
ポインターに関するすべての操作には、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 項 *
演算子では、数値オペランドの積が計算されます。
ポインター メンバー アクセス演算子 ->
->
演算子では、ポインターの間接参照とメンバー アクセスが組み合わされます。 つまり、x
が T*
型のポインターで、y
が T
型のアクセス可能なメンバーである場合に、次のような形式の式を考えます
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
は、int
、uint
、long
、または 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
と、int
、uint
、long
、または 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 つのポインター p1
と p2
の場合、式 p1 - p2
では、p1
と p2
によって指定されるアドレスの間の差を 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# 言語仕様」の次のセクションを参照してください。
関連項目
.NET