다음을 통해 공유


23 안전하지 않은 코드

23.1 일반

이 절에 정의된 구문 규칙의 사용을 진단하려면 안전하지 않은 코드를 지원하지 않는 구현이 필요합니다.

모든 하위 클로스를 포함하여 이 절의 나머지 부분에서는 조건부로 규범적입니다.

참고: 앞의 절에 정의된 핵심 C# 언어는 데이터 형식으로 포인터를 생략할 때 C 및 C++와 크게 다릅니다. 대신 C#에서는 참조 및 가비지 수집기에서 관리되는 개체를 만드는 기능을 제공합니다. 이 디자인은 다른 기능과 함께 C#을 C 또는 C++보다 훨씬 안전한 언어로 만듭니다. 핵심 C# 언어에서는 초기화되지 않은 변수, "현수" 포인터 또는 범위를 벗어난 배열을 인덱싱하는 식을 사용할 수 없습니다. 따라서 C 및 C++ 프로그램을 일상적으로 괴롭히는 버그의 전체 범주가 제거됩니다.

C 또는 C++의 거의 모든 포인터 형식 구문에는 C#에 해당되는 참조 형식이 있지만 포인터 형식에 대한 액세스가 필요한 상황이 있습니다. 예를 들어 기본 운영 체제와 상호 작용하거나, 메모리 매핑된 디바이스에 액세스하거나, 시간이 중요한 알고리즘을 구현하는 것은 포인터에 액세스하지 않고는 가능하거나 실용적이지 않을 수 있습니다. 이러한 필요성을 해결하기 위해 C#에서는 안전하지 않은 코드를 작성하는 기능을 제공합니다.

안전하지 않은 코드에서는 포인터를 선언하고 작동하고, 포인터와 정수 형식 간의 변환을 수행하고, 변수 주소를 사용하는 등의 작업을 수행할 수 있습니다. 어떤 의미에서 안전하지 않은 코드를 작성하는 것은 C# 프로그램 내에서 C 코드를 작성하는 것과 비슷합니다.

안전하지 않은 코드는 실제로 개발자와 사용자 모두의 관점에서 "안전한" 기능입니다. 안전하지 않은 코드는 한정자 unsafe로 명확하게 표시되므로 개발자는 실수로 안전하지 않은 기능을 사용할 수 없으며 실행 엔진은 신뢰할 수 없는 환경에서 안전하지 않은 코드를 실행할 수 없도록 작동합니다.

끝 메모

23.2 안전하지 않은 컨텍스트

C#의 안전하지 않은 기능은 안전하지 않은 컨텍스트에서만 사용할 수 있습니다. 형식, 멤버 또는 로컬 함수 선언에 한정자를 포함 unsafe 하거나 unsafe_statement 사용하여 안전하지 않은 컨텍스트를 도입합니다.

  • 클래스, 구조체, 인터페이스 또는 대리자의 선언에는 한정자가 포함될 unsafe 수 있습니다. 이 경우 해당 형식 선언의 전체 텍스트 범위(클래스, 구조체 또는 인터페이스의 본문 포함)는 안전하지 않은 컨텍스트로 간주됩니다.

    참고: type_declaration 부분인 경우 해당 부분만 안전하지 않은 컨텍스트입니다. 끝 메모

  • 필드, 메서드, 속성, 이벤트, 인덱서, 연산자, 인스턴스 생성자, 종료자, 정적 생성자 또는 로컬 함수의 선언에는 한정자가 포함될 unsafe 수 있습니다. 이 경우 해당 멤버 선언의 전체 텍스트 범위는 안전하지 않은 컨텍스트로 간주됩니다.
  • unsafe_statement 블록 내에서 안전하지 않은 컨텍스트를 사용할 수 있습니다. 연결된 블록 의 전체 텍스트 범위는 안전하지 않은 컨텍스트로 간주됩니다. 안전하지 않은 컨텍스트 내에서 선언된 로컬 함수 자체는 안전하지 않습니다.

연결된 문법 확장은 아래와 후속 하위 셀에 표시됩니다.

unsafe_modifier
    : 'unsafe'
    ;

unsafe_statement
    : 'unsafe' block
    ;

예제: 다음 코드에서

public unsafe struct Node
{
    public int Value;
    public Node* Left;
    public Node* Right;
}

unsafe 구조체 선언에 지정된 한정자를 사용하면 구조체 선언의 전체 텍스트 범위가 안전하지 않은 컨텍스트가 됩니다. 따라서 포인터 형식으로 Left 필드와 Right 필드를 선언할 수 있습니다. 위의 예제를 작성할 수도 있습니다.

public struct Node
{
    public int Value;
    public unsafe Node* Left;
    public unsafe Node* Right;
}

여기서 필드 선언의 unsafe 한정자는 해당 선언을 안전하지 않은 컨텍스트로 간주합니다.

끝 예제

안전하지 않은 컨텍스트를 설정하여 포인터 형식 unsafe 의 사용을 허용하는 것 외에는 한정자가 형식이나 멤버에 영향을 주지 않습니다.

예제: 다음 코드에서

public class A
{
    public unsafe virtual void F() 
    {
        char* p;
        ...
    }
}

public class B : A
{
    public override void F() 
    {
        base.F();
        ...
    }
}

메서드 F 의 안전하지 않은 한정자는 A 단순히 텍스트 범위가 언어의 F 안전하지 않은 기능을 사용할 수 있는 안전하지 않은 컨텍스트가 되도록 합니다. 재정의 F 에서 B한정자를 다시 지정할 필요가 없습니다. 물론 unsafe 메서드 자체에서 F 안전하지 않은 기능에 액세스해야 하는 경우가 아니면 한정자를 다시 지정할 B 필요가 없습니다.

포인터 형식이 메서드 서명의 일부인 경우 상황은 약간 다릅니다.

public unsafe class A
{
    public virtual void F(char* p) {...}
}

public class B: A
{
    public unsafe override void F(char* p) {...}
}

여기서는 '서명에 포인터 형식이 포함되어 있으므로 F안전하지 않은 컨텍스트에서만 작성할 수 있습니다. 그러나 안전하지 않은 컨텍스트는 전체 클래스를 안전하지 않은 상태로 만들거나 메서드 선언Aunsafe한정자를 포함하는 B 방식으로 도입할 수 있습니다.

끝 예제

unsafe 부분 형식 선언(§15.2.7)에서 한정자를 사용하는 경우 해당 특정 부분만 안전하지 않은 컨텍스트로 간주됩니다.

23.3 포인터 형식

안전하지 않은 컨텍스트에서 형식(§8.1)은 pointer_type value_type, reference_type 또는 type_parameter있습니다. 안전하지 않은 컨텍스트 에서 pointer_type 배열의 요소 형식(§17)일 수도 있습니다. pointer_type 안전하지 않은 컨텍스트 외부의 typeof 식(§12.8.18)에서도 사용할 수 있습니다(이러한 사용법은 안전하지 않음).

pointer_type unmanaged_type(§8.8void*

pointer_type
    : value_type ('*')+
    | 'void' ('*')+
    ;

포인터 형식의 * 앞에 지정된 형식을 포인터 형식의 참조 형식 이라고 합니다. 포인터 형식의 값이 가리키는 변수의 형식을 나타냅니다.

pointer_type 안전하지 않은 컨텍스트(§23.2)의 array_type만 사용할 수 있습니다. non_array_type array_type 아닌 모든 형식입니다.

참조(참조 형식의 값)와 달리 포인터는 가비지 수집기에서 추적되지 않습니다. 가비지 수집기는 포인터 및 포인터가 가리키는 데이터를 알지 않습니다. 이러한 이유로 포인터는 참조 또는 참조가 포함된 구조체를 가리킬 수 없으며 포인터의 참조 형식은 unmanaged_type 합니다. 포인터 형식 자체는 관리되지 않는 형식이므로 포인터 형식을 다른 포인터 형식의 참조 형식으로 사용할 수 있습니다.

포인터와 참조를 혼합하기 위한 직관적인 규칙은 참조(개체)의 참조에 포인터를 포함할 수 있지만 포인터 참조는 참조를 포함할 수 없다는 것입니다.

: 포인터 형식의 몇 가지 예는 아래 표에 있습니다.

예제 설명
byte* 포인터 byte
char* 포인터 char
int** 포인터에 대한 포인터 int
int*[] 에 대한 포인터의 1차원 배열 int
void* 알 수 없는 형식에 대한 포인터

끝 예제

지정된 구현의 경우 모든 포인터 형식의 크기와 표현이 같아야 합니다.

참고: C 및 C++와 달리 여러 포인터가 동일한 선언에 선언된 경우 C# * 에서는 각 포인터 이름의 접두사 문장 부호가 아니라 기본 형식과 함께 작성됩니다. 예시:

int* pi, pj; // NOT as int *pi, *pj;  

끝 메모

형식이 있는 포인터의 값은 형식 T* 변수 T의 주소를 나타냅니다. 포인터 간접 참조 연산자 * (§23.6.2)를 사용하여 이 변수에 액세스할 수 있습니다.

: 형식 P변수가 int* 지정된 경우 식 *P 은 에 포함된 주소에서 찾은 int변수를 나타냅니다P. 끝 예제

개체 참조와 마찬가지로 포인터는 다음과 같을 수 있습니다 null. -valued 포인터에 간접 참조 연산자를 null적용하면 구현 정의 동작(§23.6.2)이 발생합니다. 값 null 이 있는 포인터는 all-bits-zero로 표시됩니다.

형식은 void* 알 수 없는 형식에 대한 포인터를 나타냅니다. 참조 형식을 알 수 없으므로 간접 참조 연산자는 형식 void*의 포인터에 적용할 수 없으며 이러한 포인터에 대해 산술 연산을 수행할 수도 없습니다. 그러나 형식 void* 의 포인터는 다른 포인터 형식(또는 그 반대)으로 캐스팅할 수 있으며 다른 포인터 형식의 값(§23.6.8)과 비교할 수 있습니다.

포인터 형식은 별도의 형식 범주입니다. 참조 형식 및 값 형식과 달리 포인터 형식은 상속 object 되지 않으며 포인터 형식과 object. 특히 boxing 및 unboxing(§8.3.13)은 포인터에 대해 지원되지 않습니다. 그러나 서로 다른 포인터 형식과 포인터 형식과 정수 형식 간에 변환이 허용됩니다. 이 내용은 §23.5에 설명되어 있습니다.

pointer_type 형식 인수(§8.4)로 사용할 수 없으며 형식 인수를 포인터 형식으로 유추한 제네릭 메서드 호출에서 형식 유추(§12.6.3)가 실패합니다.

pointer_type 동적으로 바인딩된 연산(§12.3.3)의 하위 식 형식으로 사용할 수 없습니다.

pointer_type 확장 메서드(§15.6.10)에서 첫 번째 매개 변수의 형식으로 사용할 수 없습니다.

pointer_type 휘발성 필드의 형식으로 사용할 수 있습니다(§15.5.4).

E* 참조 형식이 있는 포인터 형식의 동적 지우기E입니다.

포인터 형식의 식을 사용하여 anonymous_object_creation_expression 내의 member_declarator 값을 제공할 수 없습니다(§12.8.17.7).

포인터 형식의 기본값(§9.3)은 null.

참고: 포인터를 참조 매개 변수로 전달할 수 있지만, 이렇게 하면 정의되지 않은 동작이 발생할 수 있습니다. 호출된 메서드가 반환될 때 더 이상 존재하지 않는 지역 변수를 가리키도록 포인터가 설정되거나 포인터가 가리키는 데 사용된 고정 개체가 더 이상 고정되지 않을 수 있기 때문일 수 있습니다. 예시:

class Test
{
    static int value = 20;

    unsafe static void F(out int* pi1, ref int* pi2) 
    {
        int i = 10;
        pi1 = &i;       // return address of local variable
        fixed (int* pj = &value)
        {
            // ...
            pi2 = pj;   // return address that will soon not be fixed
        }
    }

    static void Main()
    {
        int i = 15;
        unsafe 
        {
            int* px1;
            int* px2 = &i;
            F(out px1, ref px2);
            int v1 = *px1; // undefined
            int v2 = *px2; // undefined
        }
    }
}

끝 메모

메서드는 일부 형식의 값을 반환할 수 있으며 해당 형식은 포인터일 수 있습니다.

: 연속되는 시퀀스, 해당 시퀀 int스의 요소 수 및 기타 int 값에 대한 포인터를 지정하면 다음 메서드는 일치가 발생하면 해당 시퀀스에서 해당 값의 주소를 반환하고, 그렇지 않으면 다음을 반환합니다 null.

unsafe static int* Find(int* pi, int size, int value)
{
    for (int i = 0; i < size; ++i)
    {
        if (*pi == value)
        {
            return pi;
        }
        ++pi;
    }
    return null;
}

끝 예제

안전하지 않은 컨텍스트에서는 포인터에서 작동할 수 있는 몇 가지 구문을 사용할 수 있습니다.

  • 단항 * 연산자는 포인터 간접 참조(§23.6.2)를 수행하는 데 사용할 수 있습니다.
  • 연산자는 -> 포인터(§23.6.3)를 통해 구조체의 멤버에 액세스하는 데 사용할 수 있습니다.
  • 연산자를 [] 사용하여 포인터(§23.6.4)를 인덱싱할 수 있습니다.
  • 단항 & 연산자를 사용하여 변수의 주소를 가져올 수 있습니다(§23.6.5).
  • ++-- 연산자를 사용하여 포인터를 증가 및 감소할 수 있습니다(§23.6.6).
  • 이진 + 연산자와 - 연산자를 사용하여 포인터 산술 연산을 수행할 수 있습니다(§23.6.7).
  • ==, !=, <, ><=>= 연산자를 사용하여 포인터를 비교할 수 있습니다(§23.6.8).
  • 연산자를 stackalloc 사용하여 호출 스택(§23.9)에서 메모리를 할당할 수 있습니다.
  • 이 문은 fixed 해당 주소를 가져올 수 있도록 변수를 일시적으로 수정하는 데 사용할 수 있습니다(§23.7).

23.4 고정 및 이동 가능한 변수

address-of 연산자(§23.6.5)와 문(fixed)은 변수를 고정 변수와 이동 가능한 변수의 두 가지 범주로 나눕니다.

수정된 변수는 가비지 수집기 작업의 영향을 받지 않는 스토리지 위치에 상주합니다. (고정 변수의 예로는 역참조 포인터로 만든 지역 변수, 값 매개 변수 및 변수가 포함됩니다.) 반면 이동 가능한 변수는 가비지 수집기에서 재배치 또는 폐기될 수 있는 스토리지 위치에 상주합니다. 이동 가능한 변수의 예로는 개체의 필드와 배열의 요소가 포함됩니다.

& 연산자(§23.6.5)는 고정 변수의 주소를 제한 없이 가져올 수 있도록 허용합니다. 그러나 이동 가능한 변수는 가비지 수집기에 의해 재배치 또는 폐기될 수 있으므로 이동 가능한 변수의 주소는 (fixed statement)를 사용하여 서만 가져올 수 있으며 해당 주소는 해당 fixed 문의 기간 동안만 유효합니다.

정확하게 말하면 고정 변수는 다음 중 하나입니다.

다른 모든 변수는 이동 가능한 변수로 분류됩니다.

정적 필드는 이동 가능한 변수로 분류됩니다. 또한 매개 변수에 지정된 인수가 고정 변수인 경우에도 참조별 매개 변수는 이동 가능한 변수로 분류됩니다. 마지막으로 포인터를 역참조하여 생성된 변수는 항상 고정 변수로 분류됩니다.

23.5 포인터 변환

23.5.1 일반

안전하지 않은 컨텍스트에서 사용 가능한 암시적 변환 집합(§10.2)은 다음과 같은 암시적 포인터 변환을 포함하도록 확장됩니다.

  • 모든 pointer_type 형식 void*으로.
  • null 리터럴(§6.4.5.7)에서 모든 pointer_type.

또한 안전하지 않은 컨텍스트에서는 다음과 같은 명시적 포인터 변환을 포함하도록 사용 가능한 명시적 변환 집합(§10.3)이 확장됩니다.

  • 모든 pointer_type 다른 pointer_type.
  • 에서sbyte, byte, short, ushort, int, uint, long또는 ulong pointer_type.
  • 모든 sbyte , ,byteshort, ushort, intuint, long또는 ulong.

마지막으로 안전하지 않은 컨텍스트에서 표준 암시적 변환 집합(§10.4.2)에는 다음 포인터 변환이 포함됩니다.

  • 모든 pointer_type 형식 void*으로.
  • null 리터럴에서 모든 pointer_type.

두 포인터 형식 간의 변환은 실제 포인터 값을 변경하지 않습니다. 즉, 한 포인터 형식에서 다른 포인터 형식으로의 변환은 포인터에서 제공하는 기본 주소에 영향을 주지 않습니다.

한 포인터 형식이 다른 포인터 형식으로 변환될 때 결과 포인터가 뾰족한 형식에 맞게 올바르게 정렬되지 않은 경우 결과가 역참조되면 동작이 정의되지 않습니다. 일반적으로 "올바르게 정렬됨"이라는 개념은 전이적입니다. 형식에 대한 포인터가 형식 AB에 대한 포인터에 맞게 올바르게 정렬된 경우 형식에 대한 포인터 C에 대해 올바르게 정렬된 다음 형식에 대한 포인터가 형식 AC에 대한 포인터에 맞게 올바르게 정렬됩니다.

: 다른 형식에 대한 포인터를 통해 한 형식의 변수에 액세스하는 경우를 고려합니다.

unsafe static void M()
{
    char c = 'A';
    char* pc = &c;
    void* pv = pc;
    int* pi = (int*)pv; // pretend a 16-bit char is a 32-bit int
    int i = *pi;        // read 32-bit int; undefined
    *pi = 123456;       // write 32-bit int; undefined
}

끝 예제

포인터 형식이 포인터 byte로 변환되면 결과는 변수의 가장 낮은 주소를 byte 가리킵니다. 결과의 연속 증분(변수 크기까지)은 해당 변수의 나머지 바이트에 대한 포인터를 생성합니다.

: 다음 메서드는 8바이트를 double 각각 16진수 값으로 표시합니다.

class Test
{
    static void Main()
    {
        double d = 123.456e23;
        unsafe
        {
            byte* pb = (byte*)&d;
            for (int i = 0; i < sizeof(double); ++i)
            {
                Console.Write($" {*pb++:X2}");
            }
            Console.WriteLine();
        }
    }
}

물론 생성된 출력은 endianness에 따라 달라집니다. 한 가지 가능성은 " BA FF 51 A2 90 6C 24 45".

끝 예제

포인터와 정수 간의 매핑은 구현에서 정의됩니다.

참고: 그러나 선형 주소 공간이 있는 32비트 및 64비트 CPU 아키텍처에서 정수 계열 형식으로 또는 정수 형식에서 포인터를 변환하는 것은 일반적으로 이러한 정수 형식의 변환 uint 또는 ulong 값과 동일하게 동작합니다. 끝 메모

23.5.2 포인터 배열

안전하지 않은 컨텍스트에서 array_creation_expression(§12.8.17.5)를 사용하여 포인터 배열을 생성할 수 있습니다. 다른 배열 형식에 적용되는 변환 중 일부만 포인터 배열에서 허용됩니다.

  • 모든 array_type 암시적 참조 변환(System.Array)과 구현하는 인터페이스도 포인터 배열에 적용됩니다. 그러나 포인터 형식을 변환할 수 없으므로 배열 요소 System.Array 또는 구현하는 인터페이스에 액세스하려고 하면 런타임에 예외가 발생할 수 object있습니다.
  • 1차원 배열 형식 에서 제네릭 기본 인터페이스로의 암시적 및 명시적 참조 변환(S[], System.Collections.Generic.IList<T>)은 포인터 배열에 적용되지 않습니다.
  • 명시적 참조 변환(§10.3.5)System.Array과 array_type 구현하는 인터페이스가 포인터 배열에 적용됩니다.
  • 포인터 형식을 형식 인수로 사용할 수 없고 포인터 형식에서 비 포인터 형식으로 변환할 수 없으므로 1차원 배열 형식 System.Collections.Generic.IList<S> 으로의 명시적 참조 변환(T[])과 해당 기본 인터페이스에서 1차원 배열 형식으로의 변환은 포인터 배열에 적용되지 않습니다.

이러한 제한 사항은 §9.4.4.17foreach대한 문의 확장을 포인터 배열에 적용할 수 없음을 의미합니다. 대신 양식의 문입니다 foreach .

foreach (V v in x) embedded_statement

여기서 형식은 폼의 x 배열 형식이고 nT[,,...,]은 차원 수에서 1 T 을 뺀 값이거나 포인터 형식이며 다음과 같이 중첩된 for-loops를 사용하여 확장V됩니다.

{
    T[,,...,] a = x;
    for (int i0 = a.GetLowerBound(0); i0 <= a.GetUpperBound(0); i0++)
    {
        for (int i1 = a.GetLowerBound(1); i1 <= a.GetUpperBound(1); i1++)
        {
            ...
            for (int in = a.GetLowerBound(n); in <= a.GetUpperBound(n); in++) 
            {
                V v = (V)a[i0,i1,...,in];
                *embedded_statement*
            }
        }
    }
}

변수a, , i0i1... in은 프로그램의 embedded_statementx액세스할 수 없습니다. 변수 v 는 포함된 문에서 읽기 전용입니다. 명시적 변환(§23.5)이 (요소 형식)에서 T (요소 형식) V로 변환되지 않으면 오류가 발생하며 추가 단계가 수행되지 않습니다. 값 xnull 이 있는 경우 System.NullReferenceException 런타임에 throw됩니다.

참고: 포인터 형식은 형식 인수로 허용되지 않지만 포인터 배열을 형식 인수로 사용할 수 있습니다. 끝 메모

식의 23.6 포인터

23.6.1 일반

안전하지 않은 컨텍스트에서 식은 포인터 형식의 결과를 생성할 수 있지만 안전하지 않은 컨텍스트 외부에서는 식이 포인터 형식이 되는 컴파일 시간 오류입니다. 정확히 말하자면 안전하지 않은 컨텍스트 외부에서는 simple_name(§12.8.4), member_access(§12.8.7), invocation_expression(§12.8.10) 또는 element_access(§12.8.12)가 포인터 형식인 경우 컴파일 시간 오류가 발생합니다.

안전하지 않은 컨텍스트 에서 primary_no_array_creation_expression (§12.8) 및 unary_expression (§12.9) 프로덕션은 다음 하위 항목에 설명된 추가 구문을 허용합니다.

참고: 안전하지 않은 연산자의 우선 순위와 결합성은 문법에 의해 암시됩니다. 끝 메모

23.6.2 포인터 간접 참조

pointer_indirection_expression 별표(*)와 unary_expression로 구성됩니다.

pointer_indirection_expression
    : '*' unary_expression
    ;

단항 * 연산자는 포인터 간접 참조를 표시하며 포인터가 가리키는 변수를 가져오는 데 사용됩니다. 포인터 형식의 식인 계산 *PP 결과는 형식T*T의 변수입니다. 단항 * 연산자를 형식 식이나 포인터 형식 void* 이 아닌 식에 적용하는 것은 컴파일 시간 오류입니다.

단항 * 연산자를 -valued 포인터에 null적용하면 구현이 정의됩니다. 특히 이 작업이 을 throw한다는 보장은 System.NullReferenceException없습니다.

포인터에 잘못된 값이 할당된 경우 단항 * 연산자의 동작이 정의되지 않습니다.

참고: 단항 * 연산자에 의한 포인터 역참조에 대한 잘못된 값 중에는 가리키는 형식(§23.5의 예제 참조)에 대해 부적절하게 정렬된 주소와 수명이 끝난 후의 변수 주소가 있습니다.

명확한 할당 분석을 위해 양식 *P 의 식을 평가하여 생성된 변수는 처음에 할당된 것으로 간주됩니다(§9.4.2).

23.6.3 포인터 멤버 액세스

pointer_member_access primary_expression 뒤에 "" 토큰, 식별자 및 선택적 -> 이어서 구성됩니다.

pointer_member_access
    : primary_expression '->' identifier type_argument_list?
    ;

P->IP 의 포인터 멤버 액세스에서 포인터 형식의 식이어야 하며 I 가리키는 형식의 액세스 가능한 멤버를 P 나타냅니다.

P->I 의 포인터 멤버 액세스는 정확히 .로 (*P).I평가됩니다. 포인터 간접 참조 연산자()*에 대한 설명은 §23.6.2를 참조하세요. 멤버 액세스 연산자().에 대한 설명은 §12.8.7을 참조하세요.

예제: 다음 코드에서

struct Point
{
    public int x;
    public int y;
    public override string ToString() => $"({x},{y})";
}

class Test
{
    static void Main()
    {
        Point point;
        unsafe
        {
            Point* p = &point;
            p->x = 10;
            p->y = 20;
            Console.WriteLine(p->ToString());
        }
    }
}

-> 연산자는 필드에 액세스하고 포인터를 통해 구조체의 메서드를 호출하는 데 사용됩니다. 작업이 P->I 정확히 동일하기 (*P).I때문에 메서드가 Main 똑같이 잘 작성되었을 수 있습니다.

class Test
{
    static void Main()
    {
        Point point;
        unsafe
        {
            Point* p = &point;
            (*p).x = 10;
            (*p).y = 20;
            Console.WriteLine((*p).ToString());
        }
    }
}

끝 예제

23.6.4 포인터 요소 액세스

pointer_element_access primary_no_array_creation_expression 뒤에 "" 및 ""로 묶인 식으로 구성됩니다[]

pointer_element_access
    : primary_no_array_creation_expression '[' expression ']'
    ;

P[E]의 포인터 요소 액세스에서 , P 이외의 포인터 형식void*의 식이어야 하며 E 암시적으로 변환intuintlongulong할 수 있는 식이어야 합니다.

P[E] 의 포인터 요소 액세스는 정확히 .로 *(P + E)평가됩니다. 포인터 간접 참조 연산자()*에 대한 설명은 §23.6.2를 참조하세요. 포인터 추가 연산자()+에 대한 설명은 §23.6.7을 참조하세요.

예제: 다음 코드에서

class Test
{
    static void Main()
    {
        unsafe
        {
            char* p = stackalloc char[256];
            for (int i = 0; i < 256; i++)
            {
                p[i] = (char)i;
            }
        }
    }
}

포인터 요소 액세스는 루프에서 for 문자 버퍼를 초기화하는 데 사용됩니다. 작업이 P[E] 정확하게 동일하기 *(P + E)때문에 예제가 똑같이 잘 작성되었을 수 있습니다.

class Test
{
    static void Main()
    {
        unsafe
        {
            char* p = stackalloc char[256];
            for (int i = 0; i < 256; i++)
            {
                *(p + i) = (char)i;
            }
        }
    }
}

끝 예제

포인터 요소 액세스 연산자는 범위를 벗어난 오류를 확인하지 않으며 범위를 벗어난 요소에 액세스할 때의 동작은 정의되지 않습니다.

참고: C 및 C++와 동일합니다. 끝 메모

23.6.5 주소 연산자

addressof_expression 앰퍼샌드(&)와 unary_expression 앰퍼샌드로 구성됩니다.

addressof_expression
    : '&' unary_expression
    ;

형식 E 이고 고정 변수(T)로 분류되는 식 이 지정된 경우 구문 &E 은 지정된 E변수의 주소를 계산합니다. 결과의 형식은 T* 값으로 분류됩니다. 변수로 분류되지 않거나, 읽기 전용 지역 변수로 분류되거나, 이동 가능한 변수를 나타내는 경우 EE 컴파일 시간 오류가 발생 E 합니다. 마지막 경우 주소를 가져오기 전에 고정 문(§23.7)을 사용하여 변수를 일시적으로 "수정"할 수 있습니다.

참고: §12.8.7에서 설명한 것처럼 필드를 정의하는 readonly 구조체 또는 클래스의 인스턴스 생성자 또는 정적 생성자 외부에 있는 해당 필드는 변수가 아닌 값으로 간주됩니다. 따라서 해당 주소를 사용할 수 없습니다. 마찬가지로 상수의 주소를 사용할 수 없습니다. 끝 메모

& 연산자는 인수를 확실히 할당할 필요는 없지만 연산에 & 따라 연산자가 적용되는 변수는 작업이 발생하는 실행 경로에 확실히 할당된 것으로 간주됩니다. 이 상황에서 변수의 올바른 초기화가 실제로 이루어지도록 하는 것은 프로그래머의 책임입니다.

예제: 다음 코드에서

class Test
{
    static void Main()
    {
        int i;
        unsafe
        {
            int* p = &i;
            *p = 123;
        }
        Console.WriteLine(i);
    }
}

i은 초기화하는 &i데 사용되는 작업 다음에 p 확실히 할당된 것으로 간주됩니다. 실제로 할당이 *p 초기화 i되지만 이 초기화를 포함하는 것은 프로그래머의 책임이며 할당이 제거된 경우 컴파일 시간 오류가 발생하지 않습니다.

끝 예제

참고: 연산자에 & 대한 명확한 할당 규칙은 지역 변수의 중복 초기화를 방지할 수 있도록 존재합니다. 예를 들어 많은 외부 API는 API로 채워진 구조체에 대한 포인터를 사용합니다. 이러한 API에 대한 호출은 일반적으로 로컬 구조체 변수의 주소를 전달하며 규칙이 없으면 구조체 변수의 중복 초기화가 필요합니다. 끝 메모

참고: 무명 함수(§12.8.24)에서 지역 변수, 값 매개 변수 또는 매개 변수 배열을 캡처하는 경우 해당 지역 변수, 매개 변수 또는 매개 변수 배열은 더 이상 고정 변수(§23.7)로 간주되지 않지만 대신 이동 가능한 변수로 간주됩니다. 따라서 익명 함수에 의해 캡처된 지역 변수, 값 매개 변수 또는 매개 변수 배열의 주소를 가져오는 것은 안전하지 않은 코드에 대한 오류입니다. 끝 메모

23.6.6 포인터 증가 및 감소

안전하지 않은 컨텍스트 ++ 에서는 연 -- 산자(§12.8.16§12.9.6)를 제외한 void*모든 형식의 포인터 변수에 적용할 수 있습니다. 따라서 모든 포인터 형식 T*에 대해 다음 연산자는 암시적으로 정의됩니다.

T* operator ++(T* x);
T* operator --(T* x);

연산자는 각각 같은 x+1 결과를 생성합니다 x-1(§23.6.7). 즉, 형식 T*++ 의 포인터 변수에 대해 연산자는 변수에 포함된 주소에 추가하고 sizeof(T)-- 산자는 sizeof(T) 변수에 포함된 주소에서 뺍니다.

포인터 증가 또는 감소 작업이 포인터 형식의 도메인을 오버플로하는 경우 결과는 구현에서 정의되지만 예외는 생성되지 않습니다.

23.6.7 포인터 산술 연산

안전하지 않은 컨텍스트에서는 연산자 + (§12.10.5) 및 - 연산자(§12.10.6)를 제외한 void*모든 포인터 형식의 값에 적용할 수 있습니다. 따라서 모든 포인터 형식 T*에 대해 다음 연산자는 암시적으로 정의됩니다.

T* operator +(T* x, int y);
T* operator +(T* x, uint y);
T* operator +(T* x, long y);
T* operator +(T* x, ulong y);
T* operator +(int x, T* y);
T* operator +(uint x, T* y);
T* operator +(long x, T* y);
T* operator +(ulong x, T* y);
T* operator –(T* x, int y);
T* operator –(T* x, uint y);
T* operator –(T* x, long y);
T* operator –(T* x, ulong y);
long operator –(T* x, T* y);

포인터 형식의 식 PT* 형식N, intuint또는 longulongP + N식이 지정된 주소N + P에 추가 T* 된 결과로 생성되는 형식 N * sizeof(T) 의 포인터 값을 계산합니다.P 마찬가지로 식은 P – N 지정된 T*주소에서 빼 N * sizeof(T) 기에서 발생하는 형식 P 의 포인터 값을 계산합니다.

두 식 P 과 포인터 형식Q의 경우 식 T* 은 제공된 P – Q 주소 간의 차이를 계산한 P 다음 그 차이를 으로 Qsizeof(T)나눕니다. 결과의 형식은 항상 long.입니다. 실제로 P - Q 는 .로 ((long)(P) - (long)(Q)) / sizeof(T)계산됩니다.

예제:

class Test
{
    static void Main()
    {
        unsafe
        {
            int* values = stackalloc int[20];
            int* p = &values[1];
            int* q = &values[15];
            Console.WriteLine($"p - q = {p - q}");
            Console.WriteLine($"q - p = {q - p}");
        }
    }
}

출력을 생성합니다.

p - q = -14
q - p = 14

끝 예제

포인터 산술 연산이 포인터 형식의 도메인을 오버플로하는 경우 결과는 구현 정의 방식으로 잘리지만 예외는 생성되지 않습니다.

23.6.8 포인터 비교

안전하지 않은 컨텍스트에서는 모든 포인터 형식의 ==값에 , !=, <, >, <=>= 연산자(§12.12)를 적용할 수 있습니다. 포인터 비교 연산자는 다음과 같습니다.

bool operator ==(void* x, void* y);
bool operator !=(void* x, void* y);
bool operator <(void* x, void* y);
bool operator >(void* x, void* y);
bool operator <=(void* x, void* y);
bool operator >=(void* x, void* y);

모든 포인터 형식에서 형식으로의 암시적 변환이 void* 존재하기 때문에 이러한 연산자를 사용하여 포인터 형식의 피연산자를 비교할 수 있습니다. 비교 연산자는 두 피연산자가 지정한 주소를 부호 없는 정수인 것처럼 비교합니다.

23.6.9 sizeof 연산자

미리 정의된 특정 형식(§12.8.19)의 경우 연산자는 sizeof 상수 int 값을 생성합니다. 다른 모든 형식의 경우 연산자의 sizeof 결과는 구현에서 정의되며 상수가 아닌 값으로 분류됩니다.

멤버가 구조체로 압축되는 순서는 지정되지 않습니다.

맞춤을 위해 구조체의 시작 부분, 구조체 내 및 구조체의 끝에 명명되지 않은 패딩이 있을 수 있습니다. 패딩으로 사용되는 비트의 내용은 확정되지 않습니다.

구조체 형식이 있는 피연산자에 적용된 결과는 패딩을 포함하여 해당 형식의 변수에 있는 총 바이트 수입니다.

23.7 고정 문

안전하지 않은 컨텍스트 에서 embedded_statement (§13.1) 프로덕션에서는 해당 주소가 문 기간 동안 일정하게 유지되도록 이동 가능한 변수를 "수정"하는 데 사용되는 고정 문인 추가 구문을 허용합니다.

fixed_statement
    : 'fixed' '(' pointer_type fixed_pointer_declarators ')' embedded_statement
    ;

fixed_pointer_declarators
    : fixed_pointer_declarator (','  fixed_pointer_declarator)*
    ;

fixed_pointer_declarator
    : identifier '=' fixed_pointer_initializer
    ;

fixed_pointer_initializer
    : '&' variable_reference
    | expression
    ;

fixed_pointer_declarator 지정된 pointer_type 지역 변수를 선언하고 해당 fixed_pointer_initializer 계산된 주소를 사용하여 해당 지역 변수를 초기화합니다. 고정 문에 선언된 지역 변수는 해당 변수 선언의 오른쪽 및 고정 문의 embedded_statement발생하는 모든 fixed_pointer_initializer 액세스할 수 있습니다. 고정 문으로 선언된 지역 변수는 읽기 전용으로 간주됩니다. 포함된 문이 할당 또는 연산자를 통해 이 지역 변수를 수정하거나 참조 또는 ++-- 출력 매개 변수로 전달하려고 하면 컴파일 시간 오류가 발생합니다.

캡처된 지역 변수(§12.19.6.2), 값 매개 변수 또는 매개 변수 배열을 fixed_pointer_initializer 사용하는 것은 오류입니다. fixed_pointer_initializer 다음 중 하나일 수 있습니다.

  • 토큰 "&"에 이어 문지정된 포인터 형식으로 암시적으로 변환할 수 있는 경우 관리되지 않는 형식T의 이동 가능한 변수(§23.4fixed)가 잇습니다. 이 경우 이니셜라이저는 지정된 변수의 주소를 계산하고, 변수는 고정 문 기간 동안 고정된 주소로 유지됩니다.
  • 형식이 고정 문에 지정된 포인터 형식으로 암시적으로 변환할 수 있는 경우 관리되지 않는 형식 TT* 의 요소가 있는 array_type 식입니다. 이 경우 이니셜라이저는 배열에서 첫 번째 요소의 주소를 계산하고 전체 배열은 문 기간 동안 fixed 고정된 주소로 유지됩니다. 배열 식이 null 거나 배열에 요소가 0인 경우 이니셜라이저는 0과 같은 주소를 계산합니다.
  • 형식이 문에 지정된 포인터 형식 string으로 암시적으로 변환할 수 있는 경우 형식 char*fixed 식입니다. 이 경우 이니셜라이저는 문자열에서 첫 번째 문자의 주소를 계산하고 전체 문자열은 문 기간 동안 fixed 고정된 주소로 유지됩니다. 문자열 식이 fixed 면 명령문의 동작이 구현에서 정의됩니다 null.
  • 서명과 일치하는 string액세스 가능한 메서드 또는 액세스 가능한 확장 메서드가 있는 경우 array_typeT의 식입니다T* 문에 fixed 지정된 포인터 형식으로 암시적으로 변환할 수 있습니다. 이 경우 이니셜라이저는 반환된 변수의 주소를 계산하며 해당 변수는 문 기간 동안 fixed 고정된 주소로 유지됩니다. GetPinnableReference() 오버로드 확인(fixed)이 정확히 하나의 함수 멤버를 생성하고 해당 함수 멤버가 이전 조건을 충족하는 경우 문에서 메서드를 사용할 수 있습니다. 메서드는 GetPinnableReference 고정할 데이터가 없을 때 반환되는 것과 같이 0과 같은 주소에 System.Runtime.CompilerServices.Unsafe.NullRef<T>() 대한 참조를 반환해야 합니다.
  • 고정 크기 버퍼 멤버의 형식을 문에 지정된 포인터 형식으로 암시적으로 변환할 수 있는 경우 이동 가능한 변수의 고정 크기 버퍼 멤버를 참조하는 simple_name 또는 fixed member_access. 이 경우 이니셜라이저는 고정 크기 버퍼(§23.8.3)의 첫 번째 요소에 대한 포인터를 계산하고 고정 크기 버퍼는 문 기간 동안 fixed 고정 주소로 유지됩니다.

fixed_pointer_initializerfixed대해 문은 주소에서 참조하는 변수가 문 기간 동안 fixed 가비지 수집기에서 재배치 또는 폐기될 수 없도록 합니다.

: fixed_pointer_initializer 계산된 주소가 배열 인스턴스의 개체 필드 또는 요소를 참조하는 경우 고정 문은 포함된 개체 인스턴스가 문 수명 동안 재배치되거나 삭제되지 않도록 보장합니다. 끝 예제

고정 문으로 만든 포인터가 해당 문을 실행하는 것 이상으로 유지되지 않도록 하는 것은 프로그래머의 책임입니다.

: 문에서 fixed 만든 포인터가 외부 API에 전달되는 경우 API가 이러한 포인터의 메모리를 유지하지 않도록 하는 것은 프로그래머의 책임입니다. 끝 예제

고정 개체는 힙의 조각화를 일으킬 수 있습니다(이동할 수 없기 때문). 이러한 이유로 개체는 절대적으로 필요한 경우에만 수정한 다음 가능한 가장 짧은 시간 동안만 수정해야 합니다.

: 예제

class Test
{
    static int x;
    int y;

    unsafe static void F(int* p)
    {
        *p = 1;
    }

    static void Main()
    {
        Test t = new Test();
        int[] a = new int[10];
        unsafe
        {
            fixed (int* p = &x) F(p);
            fixed (int* p = &t.y) F(p);
            fixed (int* p = &a[0]) F(p);
            fixed (int* p = a) F(p);
        }
    }
}

는 문의 여러 용도를 보여 줍니다 fixed . 첫 번째 문은 정적 필드의 주소를 수정하고 가져오고, 두 번째 문은 인스턴스 필드의 주소를 수정하고 가져오며, 세 번째 문은 배열 요소의 주소를 수정하고 가져옵니다. 변수는 모두 이동 가능한 변수로 분류되므로 각 경우에 일반 & 연산자를 사용하는 것은 오류였을 것입니다.

위의 예제에서 세 번째 및 네 번째 fixed 문은 동일한 결과를 생성합니다. 일반적으로 배열 인스턴스a의 경우 문에서 a[0] 지정하는 fixed 것은 단순히 지정하는 a것과 같습니다.

끝 예제

안전하지 않은 컨텍스트에서 1차원 배열의 배열 요소는 인덱스부터 시작하여 인덱스로 0끝나는 인덱 Length – 1 스 순서로 저장됩니다. 다차원 배열의 경우 배열 요소는 맨 오른쪽 차원의 인덱스가 먼저 증가하고 다음 왼쪽 차원이 왼쪽에 증가하도록 저장됩니다.

배열 인스턴스fixedp 대한 포인터 a 를 가져오는 문 내에서 배열에 있는 요소의 주소를 나타내는 데에 pp + a.Length - 1 이르는 포인터 값입니다. 마찬가지로 실제 배열 요소를 나타내는 범위 p[0]p[a.Length - 1] 변수도 마찬가지입니다. 배열이 저장되는 방식을 고려할 때 모든 차원의 배열은 선형인 것처럼 처리될 수 있습니다.

예제:

class Test
{
    static void Main()
    {
        int[,,] a = new int[2,3,4];
        unsafe
        {
            fixed (int* p = a)
            {
                for (int i = 0; i < a.Length; ++i) // treat as linear
                {
                    p[i] = i;
                }
            }
        }
        for (int i = 0; i < 2; ++i)
        {
            for (int j = 0; j < 3; ++j)
            {
                for (int k = 0; k < 4; ++k)
                {
                    Console.Write($"[{i},{j},{k}] = {a[i,j,k],2} ");
                }
                Console.WriteLine();
            }
        }
    }
}

출력을 생성합니다.

[0,0,0] =  0 [0,0,1] =  1 [0,0,2] =  2 [0,0,3] =  3
[0,1,0] =  4 [0,1,1] =  5 [0,1,2] =  6 [0,1,3] =  7
[0,2,0] =  8 [0,2,1] =  9 [0,2,2] = 10 [0,2,3] = 11
[1,0,0] = 12 [1,0,1] = 13 [1,0,2] = 14 [1,0,3] = 15
[1,1,0] = 16 [1,1,1] = 17 [1,1,2] = 18 [1,1,3] = 19
[1,2,0] = 20 [1,2,1] = 21 [1,2,2] = 22 [1,2,3] = 23

끝 예제

예제: 다음 코드에서

class Test
{
    unsafe static void Fill(int* p, int count, int value)
    {
        for (; count != 0; count--)
        {
            *p++ = value;
        }
    }

    static void Main()
    {
        int[] a = new int[100];
        unsafe
        {
            fixed (int* p = a) Fill(p, 100, -1);
        }
    }
}

fixed 은 포인터를 사용하는 메서드에 해당 주소를 전달할 수 있도록 배열을 수정하는 데 사용됩니다.

끝 예제

char* 문자열 인스턴스를 수정하여 생성된 값은 항상 null로 끝나는 문자열을 가리킵니다. 문자열 인스턴스p에 대한 포인터 s 를 가져오는 고정 문 내에서 문자열에 있는 문자의 주소를 나타내는 포인터 pp + s.Length ‑ 1 값과 포인터 값 p + s.Length 은 항상 null 문자(값이 '\0'인 문자)를 가리킵니다.

예제:

class Test
{
    static string name = "xx";

    unsafe static void F(char* p)
    {
        for (int i = 0; p[i] != '\0'; ++i)
        {
            System.Console.WriteLine(p[i]);
        }
    }

    static void Main()
    {
        unsafe
        {
            fixed (char* p = name) F(p);
            fixed (char* p = "xx") F(p);
        }
    }
}

끝 예제

예제: 다음 코드는 array_typestring.

public class C
{
    private int _value;
    public C(int value) => _value = value;
    public ref int GetPinnableReference() => ref _value;
}

public class Test
{
    unsafe private static void Main()
    {
        C c = new C(10);
        fixed (int* p = c)
        {
            // ...
        }
    }
}

형식 C 에는 올바른 서명이 있는 액세스 가능한 GetPinnableReference 메서드가 있습니다. fixedref int 에서 호출할 때 해당 메서드에서 반환된 c 포인터를 초기화하는 int*p데 사용됩니다. 끝 예제

고정 포인터를 통해 관리되는 형식의 개체를 수정하면 정의되지 않은 동작이 발생할 수 있습니다.

참고: 예를 들어 문자열은 변경할 수 없으므로 고정 문자열에 대한 포인터에서 참조하는 문자가 수정되지 않도록 하는 것은 프로그래머의 책임입니다. 끝 메모

참고: 문자열의 자동 null 종료는 "C 스타일" 문자열을 예상하는 외부 API를 호출할 때 특히 편리합니다. 그러나 문자열 인스턴스는 null 문자를 포함할 수 있습니다. 이러한 null 문자가 있으면 null로 끝나는 char*문자열로 처리될 때 문자열이 잘린 것처럼 표시됩니다. 끝 메모

23.8 고정 크기 버퍼

23.8.1 일반

고정 크기 버퍼는 구조체의 멤버로 "C 스타일" 인라인 배열을 선언하는 데 사용되며 주로 관리되지 않는 API와 상호 작용하는 데 유용합니다.

23.8.2 고정 크기 버퍼 선언

고정 크기 버퍼는 지정된 형식의 변수의 고정 길이 버퍼에 대한 스토리지를 나타내는 멤버입니다. 고정 크기 버퍼 선언은 지정된 요소 형식의 하나 이상의 고정 크기 버퍼를 도입합니다.

참고: 배열과 마찬가지로 고정 크기 버퍼는 요소를 포함하는 것으로 간주할 수 있습니다. 따라서 배열에 정의된 용어 요소 형식 도 고정 크기 버퍼와 함께 사용됩니다. 끝 메모

고정 크기 버퍼는 구조체 선언에서만 허용되며 안전하지 않은 컨텍스트(§23.2)에서만 발생할 수 있습니다.

fixed_size_buffer_declaration
    : attributes? fixed_size_buffer_modifier* 'fixed' buffer_element_type
      fixed_size_buffer_declarators ';'
    ;

fixed_size_buffer_modifier
    : 'new'
    | 'public'
    | 'internal'
    | 'private'
    | 'unsafe'
    ;

buffer_element_type
    : type
    ;

fixed_size_buffer_declarators
    : fixed_size_buffer_declarator (',' fixed_size_buffer_declarator)*
    ;

fixed_size_buffer_declarator
    : identifier '[' constant_expression ']'
    ;

고정 크기 버퍼 선언에는 특성 집합(§22), new 한정자(§15.3.5), 구조체 멤버에 허용되는 선언된 액세스 권한에 해당하는 접근성 한정자(§16.4.3) 및 unsafe 한정자(§23.2)가 포함될 수 있습니다. 특성 및 한정자는 고정 크기 버퍼 선언에 의해 선언된 모든 멤버에 적용됩니다. 동일한 한정자가 고정 크기 버퍼 선언에 여러 번 표시되는 것은 오류입니다.

고정 크기 버퍼 선언은 한정자를 포함 static 할 수 없습니다.

고정 크기 버퍼 선언의 버퍼 요소 형식은 선언에 의해 도입된 버퍼의 요소 형식을 지정합니다. 버퍼 요소 형식은 미리 정의된 형식sbyte, ,byte, short, ushortint, uintlong, ulong, charfloatdouble또는 .bool

버퍼 요소 형식 뒤에는 고정 크기 버퍼 선언자 목록이 잇고 각각 새 멤버가 도입됩니다. 고정 크기 버퍼 선언자는 멤버 이름을 지정하는 식별자와 토큰으로 묶 [] 인 상수 식으로 구성됩니다. 상수 식은 해당 고정 크기 버퍼 선언자가 도입한 멤버의 요소 수를 표시합니다. 상수 식의 형식은 암시적으로 형식 int으로 변환할 수 있어야 하며 값은 0이 아닌 양의 정수여야 합니다.

고정 크기 버퍼의 요소는 메모리에 순차적으로 배치되어야 합니다.

여러 고정 크기 버퍼를 선언하는 고정 크기 버퍼 선언은 동일한 특성 및 요소 형식을 가진 단일 고정 크기 버퍼 선언의 여러 선언과 동일합니다.

예제:

unsafe struct A
{
    public fixed int x[5], y[10], z[100];
}

위의 식은 아래의 식과 동일합니다.

unsafe struct A
{
    public fixed int x[5];
    public fixed int y[10];
    public fixed int z[100];
}

끝 예제

23.8.3 식의 고정 크기 버퍼

고정 크기 버퍼 멤버의 멤버 조회(§12.5)는 필드의 멤버 조회와 똑같이 진행됩니다.

고정 크기 버퍼는 simple_name(§12.8.4), member_access(§12.8.7) 또는 element_access(§12.8.12)를 사용하여 식에서 참조할 수 있습니다.

고정 크기 버퍼 멤버를 단순 이름으로 참조하는 경우 효과는 고정 크기 버퍼 멤버인 폼 this.II 의 멤버 액세스와 동일합니다.

암시적E.I일 수 있는 폼 E. 의 멤버 액세스에서 this. 구조체 형식이고 해당 구조체 형식의 E 멤버 조회가 고정 크기 멤버 I 를 식별하는 경우 E.I 다음과 같이 평가되고 분류됩니다.

  • 안전하지 않은 컨텍스트에서 식 E.I 이 발생하지 않으면 컴파일 시간 오류가 발생합니다.
  • 값으로 분류되는 경우 E 컴파일 시간 오류가 발생합니다.
  • 그렇지 않으면 이동 가능한 변수(E)이면 다음을 수행합니다.
  • 그렇지 않으면 E 고정 변수를 참조하고 식의 결과는 고정 크기 버퍼 멤버 I 의 첫 번째 요소에 E대한 포인터입니다. 결과는 형식 S*입니다. 여기서 S는 요소 형식 I이며 값으로 분류됩니다.

고정 크기 버퍼의 후속 요소는 첫 번째 요소의 포인터 작업을 사용하여 액세스할 수 있습니다. 배열에 대한 액세스와 달리 고정 크기 버퍼의 요소에 대한 액세스는 안전하지 않은 작업이며 범위를 확인하지 않습니다.

: 다음은 고정 크기 버퍼 멤버가 있는 구조체를 선언하고 사용합니다.

unsafe struct Font
{
    public int size;
    public fixed char name[32];
}

class Test
{
    unsafe static void PutString(string s, char* buffer, int bufSize)
    {
        int len = s.Length;
        if (len > bufSize)
        {
            len = bufSize;
        }
        for (int i = 0; i < len; i++)
        {
            buffer[i] = s[i];
        }
        for (int i = len; i < bufSize; i++)
        {
            buffer[i] = (char)0;
        }
    }

    unsafe static void Main()
    {
        Font f;
        f.size = 10;
        PutString("Times New Roman", f.name, 32);
    }
}

끝 예제

23.8.4 배정 확인

고정 크기 버퍼는 명확한 할당 검사(§9.4)의 적용을 받지 않으며 구조체 형식 변수의 명확한 할당 검사를 위해 고정 크기 버퍼 멤버는 무시됩니다.

고정 크기 버퍼 멤버의 구조체 변수를 포함하는 가장 바깥쪽 변수가 정적 변수, 클래스 인스턴스의 인스턴스 변수 또는 배열 요소인 경우 고정 크기 버퍼의 요소는 기본값(§9.3)으로 자동으로 초기화됩니다. 다른 모든 경우에서는 고정 크기 버퍼의 초기 콘텐츠가 정의되지 않습니다.

23.9 스택 할당

운영자에 대한 일반 정보는 §12.8.22를 참조하세요.stackalloc 여기서는 포인터를 생성하는 해당 연산자의 기능에 대해 설명합니다.

stackalloc_expression 포인터 형식(§23 local_variable_type)인 local_variable_declaration(§13.6.2)의 초기화 식으로 발생하는 경우 .3) 또는 유추(var) stackalloc_expression 결과는 T*형식의 포인터입니다. 여기서 Tstackalloc_expressionunmanaged_type. 이 경우 결과는 할당된 블록의 시작 포인터입니다.

예제:

unsafe 
{
    // Memory uninitialized
    int* p1 = stackalloc int[3];
    // Memory initialized
    int* p2 = stackalloc int[3] { -10, -15, -30 };
    // Type int is inferred
    int* p3 = stackalloc[] { 11, 12, 13 };
    // Can't infer context, so pointer result assumed
    var p4 = stackalloc[] { 11, 12, 13 };
    // Error; no conversion exists
    long* p5 = stackalloc[] { 11, 12, 13 };
    // Converts 11 and 13, and returns long*
    long* p6 = stackalloc[] { 11, 12L, 13 };
    // Converts all and returns long*
    long* p7 = stackalloc long[] { 11, 12, 13 };
}

끝 예제

배열 또는 stackalloc'ed 형식 블록에 Span<T> 대한 액세스와 달리 포인터 형식의 stackalloced 블록 요소에 대한 액세스는 안전하지 않은 작업이며 범위를 선택하지 않습니다.

예제: 다음 코드에서

class Test
{
    static string IntToString(int value)
    {
        if (value == int.MinValue)
        {
            return "-2147483648";
        }
        int n = value >= 0 ? value : -value;
        unsafe
        {
            char* buffer = stackalloc char[16];
            char* p = buffer + 16;
            do
            {
                *--p = (char)(n % 10 + '0');
                n /= 10;
            } while (n != 0);
            if (value < 0)
            {
                *--p = '-';
            }
            return new string(p, 0, (int)(buffer + 16 - p));
        }
    }

    static void Main()
    {
        Console.WriteLine(IntToString(12345));
        Console.WriteLine(IntToString(-999));
    }
}

stackalloc 식은 스택에 IntToString 16자의 버퍼를 할당하기 위해 메서드에 사용됩니다. 메서드가 반환되면 버퍼가 자동으로 삭제됩니다.

그러나 IntToString 다음과 같이 포인터를 사용하지 않고 안전 모드로 다시 작성할 수 있습니다.

class Test
{
    static string IntToString(int value)
    {
        if (value == int.MinValue)
        {
            return "-2147483648";
        }
        int n = value >= 0 ? value : -value;
        Span<char> buffer = stackalloc char[16];
        int idx = 16;
        do
        {
            buffer[--idx] = (char)(n % 10 + '0');
            n /= 10;
        } while (n != 0);
        if (value < 0)
        {
            buffer[--idx] = '-';
        }
        return buffer.Slice(idx).ToString();
    }
}

끝 예제

조건부 표준 텍스트의 끝입니다.