포인터 관련 연산자 - 변수의 주소 가져오기, 스토리지 위치 역참조, 메모리 위치 액세스

포인터 연산자를 사용하면 변수(&)의 주소를 가져오고, 포인터(*)를 역참조하고, 포인터 값을 비교하고, 포인터와 정수를 더하거나 뺄 수 있습니다.

다음 연산자를 사용하여 포인터 작업을 할 수 있습니다.

포인터 형식에 대한 내용은 포인터 형식을 참조하세요.

참고 항목

포인터를 사용한 작업에는 안전하지 않은 컨텍스트가 필요합니다. 안전하지 않은 블록을 포함하는 코드는 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으로 "fix" 또는 "pin"으로 할 경우 이동 가능한 변수의 주소를 가져올 수 있습니다. 가져온 주소는 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# 언어 사양고정 및 이동 가능 변수 섹션을 참조하세요.

이진 & 연산자는 해당 부울 피연산자의 논리 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* 형식의 식에 적용할 수 없습니다.

이진 * 연산자는 해당 숫자 피연산자의 을 컴퓨팅합니다.

포인터 멤버 액세스 연산자 ->

-> 연산자는 포인터 간접 참조멤버 액세스를 결합합니다. 즉, 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은 암시적으로 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* 형식의 식으로 포인터 요소 액세스에 []를 사용할 수 없습니다.

배열 요소 또는 인덱서 액세스에 대해 [] 연산자를 사용할 수도 있습니다.

포인터 산술 연산자

포인터를 사용하여 다음과 같은 산술 연산을 수행할 수 있습니다.

  • 포인터로 또는 포인터에서 정수 값 추가 또는 빼기
  • 두 포인터 빼기
  • 포인터 증가 또는 감소

void* 형식의 포인터를 사용하여 이러한 작업을 수행할 수 없습니다.

숫자 형식으로 지원되는 산술 연산에 대한 내용은 산술 연산자를 참조하세요.

포인터로 또는 포인터에서 정수 값 더하기 또는 빼기

T* 형식의 포인터 pint, uint, long 또는 ulong으로 암시적으로 변환할 수 있는 형식의 n 식에 대한 더하기와 빼기가 다음과 같이 정의됩니다.

  • p + nn + 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* 형식의 두 가지 포인터 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)의 두 가지 형태로 지원됩니다. 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*를 포함한 모든 포인터 형식의 피연산자를 비교할 수 있습니다. 이러한 연산자는 두 피연산자가 지정한 주소를 부호 없는 정수인 것처럼 비교합니다.

다른 형식의 피연산자에 대한 해당 연산자의 동작에 대한 정보는 항등 연산자비교 연산자 문서를 참조하세요.

연산자 우선 순위

다음 목록에서는 포인터 관련 연산자를 가장 높은 우선순위부터 가장 낮은 것으로 정렬합니다.

  • 접미사 증가 x++ 및 감소 x-- 연산자와 ->[] 연산자
  • 접두사 증가 ++x 및 감소 --x 연산자와 &* 연산자
  • 가감 +- 연산자
  • 비교 <, >, <=>= 연산자
  • 같음 ==!= 연산자

괄호(())를 사용하여 연산자 우선순위에 따라 주어진 평가 순서를 변경합니다.

우선 순위 수준에 따라 정렬된 전체 연산자 목록은 C# 연산자 문서의 연산자 우선 순위 섹션을 참조하세요.

연산자 오버로드 가능성

사용자 정의 형식은 포인터 관련 연산자 &, *, ->, []를 오버로드할 수 없습니다.

C# 언어 사양

자세한 내용은 C# 언어 사양의 다음 섹션을 참조하세요.

참고 항목