다음을 통해 공유


네이티브 크기의 정수

메모

이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.

기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는 관련 LDM(언어 디자인 모임) 노트에 기록됩니다.

사양문서에서 기능 사양서를 C# 언어 표준으로 채택하는 프로세스에 대해 더 자세히 알아볼 수 있습니다.

요약

네이티브 크기의 부호 있는 정수 및 부호 없는 정수 형식에 대한 언어 지원입니다.

동기는 인터롭 시나리오 및 저수준 라이브러리를 위한 것입니다.

디자인

nintnuint 식별자는 네이티브 부호 있는 정수 및 부호 없는 정수 형식을 나타내는 새로운 컨텍스트 키워드입니다. 식별자는 이름 조회가 해당 프로그램 위치에서 실행 가능한 결과를 찾지 못하는 경우에만 키워드로 처리됩니다.

nint x = 3;
_ = nint.Equals(x, 3);

nintnuint 형식은 해당 형식에 대한 추가 변환 및 연산을 네이티브 ints로 표시하는 컴파일러와 함께 System.IntPtrSystem.UIntPtr 기본 형식으로 표시됩니다.

상수

상수 표현식은 nint 형식 또는 nuint형식일 수 있습니다. 네이티브 int 리터럴에 대한 직접적인 구문은 없습니다. 다른 정수 상수 값의 암시적 또는 명시적 캐스트를 대신 사용할 수 있습니다. const nint i = (nint)42;.

nint 상수는 [int.MinValue, int.MaxValue ] 범위에 있습니다.

nuint 상수는 [uint.MinValue, uint.MaxValue ] 범위에 있습니다.

nuint.MinValue이외의 값들은 상수로 출력되지 않기 때문에, nintnuint에는 MinValueMaxValue 필드가 없습니다.

상수 접기는 모든 단항 연산자 { +, -, ~ } 및 이진 연산자 { +, -, *, /, %, ==, !=, <, <=, >, >=, &, |, ^, <<, >> }에 대해 지원됩니다. 상수 접기 작업은 컴파일러 플랫폼에 관계없이 일관된 동작을 위해 네이티브 int가 아닌 Int32UInt32 피연산자를 사용하여 평가됩니다. 연산 결과가 32비트 내의 상수 값인 경우, 컴파일 시에 상수 폴딩이 수행됩니다. 그렇지 않으면 작업이 런타임에 실행되고 상수로 간주되지 않습니다.

변환

nint IntPtrnuintUIntPtr간에 ID 변환이 있습니다. 네이티브 int와 기본 형식만 다른 복합 형식 간에, 배열, Nullable<>, 생성된 형식, 그리고 튜플에 대한 동일 변환이 존재합니다.

아래 표에서는 특수 형식 간의 변환을 다룹니다. (각 변환에 대한 IL에는 uncheckedchecked 컨텍스트에 대한 변형이 포함됩니다(다른 경우).

아래 표의 일반 참고 사항:

  • conv.u 네이티브 정수로의 0 확장 변환이며 conv.i 네이티브 정수로의 부호 확장 변환입니다.
  • 확대축소 모두에 대한 checked 컨텍스트는 다음과 같습니다.
    • signed to *에 대한 conv.ovf.*
    • unsigned to * 위한 conv.ovf.*.un
  • 다음은 확대를 위한unchecked 컨텍스트입니다.
    • conv.i* signed to *을 위한 (여기서 *는 대상 너비)
    • conv.u* unsigned to *(여기서 *는 대상 너비를 나타냅니다)
  • unchecked 축소을 위한 맥락은 다음과 같습니다.
    • conv.i* any to signed *용 (여기서 *는 대상 너비)
    • conv.u* any to unsigned *(여기서 *는 대상 너비)

몇 가지 예제를 사용합니다.

  • sbyte to nintsbyte to nuintconv.i 사용하는 반면 byte to nintbyte to nuint 모두 확장conv.u 사용합니다.
  • nint to bytenuint to byteconv.u1를 사용하고, nint to sbytenuint to sbyteconv.i1를 사용합니다. byte, sbyte, short, 및 ushort의 "스택 유형"은 int32입니다. 따라서 conv.i1는 효과적으로 "부호 있는 바이트로 다운캐스트한 다음 int32까지 부호 확장"하는 것이며, conv.u1은 효과적으로 "부호 없는 바이트로 다운캐스트한 다음 int32까지 0 확장"하는 것입니다.
  • checked void* to nintchecked void* to longconv.ovf.i8.un을 사용하는 방식과 동일하게 conv.ovf.i.un을 사용합니다.
피연산자 타겟 변환 IL(IL)
object nint 제품 개봉 unbox
void* nint PointerToVoid nop / conv.ovf.i.un
sbyte nint 암시적 숫자 conv.i
byte nint ImplicitNumeric conv.u
short nint ImplicitNumeric conv.i
ushort nint 암묵적 숫자 conv.u
int nint ImplicitNumeric conv.i
uint nint 명시적 숫자 conv.u / conv.ovf.i.un
long nint 명시적 숫자 conv.i / conv.ovf.i
ulong nint 명시적 숫자 conv.i / conv.ovf.i.un
char nint ImplicitNumeric conv.u
float nint 명시적 수치 conv.i / conv.ovf.i
double nint 명시적 숫자 conv.i / conv.ovf.i
decimal nint 명시적 수치 long decimal.op_Explicit(decimal) conv.i / ... conv.ovf.i
IntPtr nint 신원
UIntPtr nint 없음
object nuint 언박싱 unbox
void* nuint PointerToVoid nop
sbyte nuint 명시적 숫자 conv.i / conv.ovf.u
byte nuint 암시적 숫자 conv.u
short nuint 엑스플리싯누메릭 conv.i / conv.ovf.u
ushort nuint 암시적 숫자 conv.u
int nuint 명시적 숫자 conv.i / conv.ovf.u
uint nuint 암묵적 수치 conv.u
long nuint 명시적 숫자 conv.u / conv.ovf.u
ulong nuint 명시적 수치 conv.u / conv.ovf.u.un
char nuint 암시적 숫자 conv.u
float nuint 명시적 수치 conv.u / conv.ovf.u
double nuint 명시적 숫자 conv.u / conv.ovf.u
decimal nuint 명시적 수치 ulong decimal.op_Explicit(decimal) conv.u / ... conv.ovf.u.un
IntPtr nuint 없음
UIntPtr nuint 신원
열거 nint 명시적 열거
열거형 nuint 명시적 열거
피연산자 목표 변환 IL
nint object 권투 box
nint void* 포인터투보이드 nop / conv.ovf.u
nint nuint 명시적숫자 conv.u(생략 가능) / conv.ovf.u
nint sbyte 명시적 숫자 conv.i1 / conv.ovf.i1
nint byte 명시적 숫자 conv.u1 / conv.ovf.u1
nint short ExplicitNumeric conv.i2 / conv.ovf.i2
nint ushort 명시적 수치 conv.u2 / conv.ovf.u2
nint int 명시적 숫자 conv.i4 / conv.ovf.i4
nint uint 명시적 숫자 conv.u4 / conv.ovf.u4
nint long ImplicitNumeric conv.i8
nint ulong 명시적 숫자 conv.i8 / conv.ovf.u8
nint char 명시적 숫자 conv.u2 / conv.ovf.u2
nint float 암시적 숫자 conv.r4
nint double 암시적 숫자 conv.r8
nint decimal 암묵적 숫자 conv.i8 decimal decimal.op_Implicit(long)
nint IntPtr 신원
nint UIntPtr 없음
nint 열거 명시적 열거
nuint object 권투 box
nuint void* 포인터 투 보이드 nop
nuint nint 명시적 숫자 conv.i(생략 가능) / conv.ovf.i.un
nuint sbyte 명시적 수치 conv.i1 / conv.ovf.i1.un
nuint byte 명시적 숫자 conv.u1 / conv.ovf.u1.un
nuint short ExplicitNumeric conv.i2 / conv.ovf.i2.un
nuint ushort 명시적 숫자 conv.u2 / conv.ovf.u2.un
nuint int 명시적 숫자 conv.i4 / conv.ovf.i4.un
nuint uint 명시적 숫자 conv.u4 / conv.ovf.u4.un
nuint long 명시적 숫자 conv.u8 / conv.ovf.i8.un
nuint ulong 암시적 숫자 conv.u8
nuint char 명시적숫자 conv.u2 / conv.ovf.u2.un
nuint float 암묵적 수치 conv.r.un conv.r4
nuint double 암시적 숫자 conv.r.un conv.r8
nuint decimal 암시적수치 conv.u8 decimal decimal.op_Implicit(ulong)
nuint IntPtr 없음
nuint UIntPtr 신원
nuint 열거 명시적 열거

A에서 Nullable<B>로의 변환은 다음과 같습니다.

  • id 변환 또는 A에서 B로의 암시적 변환이 있을 때, 암시적 nullable 변환이 존재합니다.
  • A에서 B로의 명시적 변환이 있는 경우 명시적 nullable 변환
  • 그렇지 않으면 유효하지 않습니다.

Nullable<A>에서 B로의 변환은 다음과 같습니다.

  • A에서 B로 ID 변환이 있거나 암시적 또는 명시적 숫자 변환이 있는 경우 명시적 nullable 변환
  • 그렇지 않으면 유효하지 않습니다.

Nullable<A>에서 Nullable<B>로의 변환 결과는 다음과 같습니다.

  • A에서 B으로의 id 변환이 있다면;
  • A에서 B로 암시적 또는 명시적 숫자 변환이 있는 경우 명시적으로 널러블로 변환합니다.
  • 그렇지 않으면 유효하지 않습니다.

연산자

미리 정의된 연산자는 다음과 같습니다. 이러한 연산자는 피연산자 중 하나 이상이 형식이거나 형식인 경우, 암시적 변환에 대한 일반 규칙에 따라 오버로드 해석 중에 고려됩니다.

(각 연산자에 대한 IL에는 uncheckedchecked 컨텍스트에 대한 변형이 포함되어 있습니다(다른 경우).

단항 연산자 서명 IL
+ nint operator +(nint value) nop
+ nuint operator +(nuint value) nop
- nint operator -(nint value) neg
~ nint operator ~(nint value) not
~ nuint operator ~(nuint value) not
바이너리 연산자 서명 IL
+ nint operator +(nint left, nint right) add / add.ovf
+ nuint operator +(nuint left, nuint right) add / add.ovf.un
- nint operator -(nint left, nint right) sub / sub.ovf
- nuint operator -(nuint left, nuint right) sub / sub.ovf.un
* nint operator *(nint left, nint right) mul / mul.ovf
* nuint operator *(nuint left, nuint right) mul / mul.ovf.un
/ nint operator /(nint left, nint right) div
/ nuint operator /(nuint left, nuint right) div.un
% nint operator %(nint left, nint right) rem
% nuint operator %(nuint left, nuint right) rem.un
== bool operator ==(nint left, nint right) beq / ceq
== bool operator ==(nuint left, nuint right) beq / ceq
!= bool operator !=(nint left, nint right) bne
!= bool operator !=(nuint left, nuint right) bne
< bool operator <(nint left, nint right) blt / clt
< bool operator <(nuint left, nuint right) blt.un / clt.un
<= bool operator <=(nint left, nint right) ble
<= bool operator <=(nuint left, nuint right) ble.un
> bool operator >(nint left, nint right) bgt / cgt
> bool operator >(nuint left, nuint right) bgt.un / cgt.un
>= bool operator >=(nint left, nint right) bge
>= bool operator >=(nuint left, nuint right) bge.un
& nint operator &(nint left, nint right) and
& nuint operator &(nuint left, nuint right) and
| nint operator |(nint left, nint right) or
| nuint operator |(nuint left, nuint right) or
^ nint operator ^(nint left, nint right) xor
^ nuint operator ^(nuint left, nuint right) xor
<< nint operator <<(nint left, int right) shl
<< nuint operator <<(nuint left, int right) shl
>> nint operator >>(nint left, int right) shr
>> nuint operator >>(nuint left, int right) shr.un

일부 이진 연산자의 경우 IL 연산자는 추가 피연산자 형식을 지원합니다(ECMA-335 III.1.5 피연산자 형식 테이블 참조). 그러나 C#에서 지원하는 피연산자 형식 집합은 단순성과 언어의 기존 연산자와의 일관성을 위해 제한됩니다.

인수와 반환 형식이 nint?nuint?인 연산자의 올림 버전이 지원됩니다.

복합 할당 작업 x op= yx 또는 y 네이티브 int인 경우, 미리 정의된 연산자가 있는 다른 기본 형식과 동일한 규칙을 따릅니다. 특히 이 식은 Tx 형식이고, x이 한 번만 평가되는 조건에서 x = (T)(x op y)에 바인딩됩니다.

시프트 연산자는 이동할 비트 수를 마스킹해야 합니다. sizeof(nint) 4이면 5비트, sizeof(nint) 8이면 6비트입니다. C# 사양의 §12.11참조).

C#9 컴파일러는 이전 언어 버전으로 컴파일할 때 미리 정의된 네이티브 정수 연산자에 대한 바인딩 오류를 보고하지만, 네이티브 정수로의 그리고 네이티브 정수로부터의 미리 정의된 변환은 허용합니다.

csc -langversion:9 -t:library A.cs

public class A
{
    public static nint F;
}

csc -langversion:8 -r:A.dll B.cs

class B : A
{
    static void Main()
    {
        F = F + 1; // error: nint operator+ not available with -langversion:8
        F = (System.IntPtr)F + 1; // ok
    }
}

포인터 산술 연산

C#에는 네이티브 정수 오프셋이 있는 포인터 추가 또는 빼기용 미리 정의된 연산자가 없습니다. 대신 nintnuint 값이 longulong로 승격되며, 포인터 산술에는 해당 형식에 미리 정의된 연산자들이 사용됩니다.

static T* AddLeftS(nint x, T* y) => x + y;   // T* operator +(long left, T* right)
static T* AddLeftU(nuint x, T* y) => x + y;  // T* operator +(ulong left, T* right)
static T* AddRightS(T* x, nint y) => x + y;  // T* operator +(T* left, long right)
static T* AddRightU(T* x, nuint y) => x + y; // T* operator +(T* left, ulong right)
static T* SubRightS(T* x, nint y) => x - y;  // T* operator -(T* left, long right)
static T* SubRightU(T* x, nuint y) => x - y; // T* operator -(T* left, ulong right)

이진 숫자 승격

이진 숫자 승격 정보 텍스트(C# 사양의 §12.4.7.3참조)는 다음과 같이 업데이트됩니다.

  • 그렇지 않으면 피연산자 중 하나가 ulong형식이거나, 다른 피연산자는 ulong형식으로 변환되거나, 다른 피연산자 형식이 sbyte, short, int, nint또는 long경우 바인딩 시간 오류가 발생합니다.
  • 피연산자 중 하나가 nuint형식인 경우, 다른 피연산자는 nuint형식으로 변환됩니다. 그러나 만약 다른 피연산자가 sbyte, short, int, nint, 또는 long형식이면, 바인딩 시간 오류가 발생합니다.
  • 그렇지 않으면 피연산자 중 하나가 long형식이면 다른 피연산자는 long형식으로 변환됩니다.
  • 그렇지 않으면 피연산자 중 하나가 uint 형식이고 다른 피연산자 형식이 sbyte, short, nint, 또는 int경우 두 피연산자는 모두 형식 long변환됩니다.
  • 그렇지 않으면 피연산자 중 하나가 uint형식이면 다른 피연산자는 uint형식으로 변환됩니다.
  • 그렇지 않으면 피연산자 중 하나가 nint형식이면 다른 피연산자는 nint형식으로 변환됩니다.
  • 그렇지 않으면 두 피연산자는 모두 int형식으로 변환됩니다.

동적인

변환 및 연산자는 컴파일러에서 합성되며 기본 IntPtrUIntPtr 형식의 일부가 아닙니다. 따라서 이러한 변환 및 연산자 은 런타임 바인더 dynamic에서 사용할 수 없습니다.

nint x = 2;
nint y = x + x; // ok
dynamic d = x;
nint z = d + x; // RuntimeBinderException: '+' cannot be applied 'System.IntPtr' and 'System.IntPtr'

타입 멤버

nint 또는 nuint의 유일한 생성자는 매개 변수가 없는 생성자입니다.

System.IntPtrSystem.UIntPtr다음 멤버는 nint 또는 nuint 명시적으로 제외됩니다.

// constructors
// arithmetic operators
// implicit and explicit conversions
public static readonly IntPtr Zero; // use 0 instead
public static int Size { get; }     // use sizeof() instead
public static IntPtr Add(IntPtr pointer, int offset);
public static IntPtr Subtract(IntPtr pointer, int offset);
public int ToInt32();
public long ToInt64();
public void* ToPointer();

System.IntPtrSystem.UIntPtr나머지 멤버는 nintnuint 암시적으로 포함됩니다. .NET Framework 4.7.2의 경우:

public override bool Equals(object obj);
public override int GetHashCode();
public override string ToString();
public string ToString(string format);

System.IntPtrSystem.UIntPtr에 의해 구현된 인터페이스는 nintnuint 암시적으로 포함되며, 기본 형식은 해당 네이티브 정수 형식으로 대체됩니다. 예를 들어 IntPtrISerializable, IEquatable<IntPtr>, IComparable<IntPtr>구현하는 경우 nintISerializable, IEquatable<nint>, IComparable<nint>구현합니다.

오버라이딩, 숨기기 및 구현

nintSystem.IntPtr, 그리고 nuintSystem.UIntPtr는 재정의, 숨기기 및 구현에서 동등한 것으로 간주됩니다.

오버로드는 nintSystem.IntPtr, 그리고 nuintSystem.UIntPtr만으로는 구분될 수 없습니다. 재정의 및 구현은 nintSystem.IntPtr, 또는 nuintSystem.UIntPtr에 따라 다를 수 있습니다. 메서드는 nintSystem.IntPtr로 다르거나 nuintSystem.UIntPtr로 다른 메서드를 단독으로 숨깁니다.

잡다한

배열 인덱스로 사용되는 nintnuint 식은 변환 없이 내보내집니다.

static object GetItem(object[] array, nint index)
{
    return array[index]; // ok
}

nintnuint C#에서 enum 기본 형식으로 사용할 수 없습니다.

enum E : nint // error: byte, sbyte, short, ushort, int, uint, long, or ulong expected
{
}

읽기와 쓰기는 nintnuint에 대해 원자성을 가집니다.

필드는 형식 nintnuint에 대해 volatile으로 표시될 수 있습니다. ECMA-334 15.5.4에는 기본 형식 System.IntPtr 또는 System.UIntPtr과(와) 함께 enum가 포함되어 있지 않습니다.

default(nint)new nint()(nint)0에 해당하고; default(nuint)new nuint()(nuint)0에 해당합니다.

typeof(nint)typeof(IntPtr)이다 ; typeof(nuint)typeof(UIntPtr)이다.

sizeof(nint)sizeof(nuint) 지원되지만 안전하지 않은 컨텍스트에서 컴파일해야 합니다(sizeof(IntPtr)sizeof(UIntPtr)필요). 값은 컴파일 시간 상수가 아닙니다. sizeof(nint) IntPtr.Size대신 sizeof(IntPtr) 구현됩니다. sizeof(nuint)UIntPtr.Size대신 sizeof(UIntPtr) 구현됩니다.

컴파일러 진단은 IntPtr 또는 UIntPtr가 아닌 nint 또는 nuint과 관련된 형식 참조에 대해 nint 또는 nuint을 보고합니다.

메타데이터

nintnuint은 메타데이터에서 System.IntPtrSystem.UIntPtr으로 표현됩니다.

nint 또는 nuint을 포함하는 형식 참조는 형식 참조의 어느 부분이 네이티브 int인지 나타내기 위해 System.Runtime.CompilerServices.NativeIntegerAttribute와 함께 출력됩니다.

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(
        AttributeTargets.Class |
        AttributeTargets.Event |
        AttributeTargets.Field |
        AttributeTargets.GenericParameter |
        AttributeTargets.Parameter |
        AttributeTargets.Property |
        AttributeTargets.ReturnValue,
        AllowMultiple = false,
        Inherited = false)]
    public sealed class NativeIntegerAttribute : Attribute
    {
        public NativeIntegerAttribute()
        {
            TransformFlags = new[] { true };
        }
        public NativeIntegerAttribute(bool[] flags)
        {
            TransformFlags = flags;
        }
        public readonly bool[] TransformFlags;
    }
}

형식 참조의 인코딩 NativeIntegerAttributeNativeIntegerAttribute.md에서 설명되어 있습니다.

대안

위의 "형식 지우기" 방법 대신 새 형식인 System.NativeIntSystem.NativeUInt도입합니다.

public readonly struct NativeInt
{
    public IntPtr Value;
}

고유한 유형은 IntPtr와 별개의 오버로드를 허용하고, 고유한 구문 분석 및 ToString()을 허용합니다. 그러나 CLR이 이러한 형식을 효율적으로 처리하기 위해 더 많은 작업이 필요하게 되어, 이는 기능의 주요 목적인 효율성에 반하게 됩니다. 또한 IntPtr 사용하는 기존 네이티브 int 코드와의 상호 운용이 더 어려울 수 있습니다.

또 다른 대안은 특정 컴파일러 지원 없이 프레임워크에서 IntPtr에 대한 추가적인 네이티브 int 지원을 추가하는 것입니다. 모든 새 변환 및 산술 연산은 컴파일러에서 자동으로 지원됩니다. 그러나 언어는 키워드, 상수 또는 checked 작업을 제공하지 않습니다.

디자인 회의