왜 C # 컴파일러가이 비교를 마치> 비교 인 것처럼 해석합니까?


147

나는 우연히 C # 컴파일러 가이 방법을 돌리는 것을 발견했다.

static bool IsNotNull(object obj)
{
    return obj != null;
}

…이 CIL에 :

.method private hidebysig static bool IsNotNull(object obj) cil managed
{
    ldarg.0   // obj
    ldnull
    cgt.un
    ret
}

또는 디 컴파일 된 C # 코드를 선호하는 경우 :

static bool IsNotNull(object obj)
{
    return obj > null;   // (note: this is not a valid C# expression)
}

!=" >" 로 번역 된 결과는 어떻게 됩니까?

답변:


201

짧은 답변:

IL에는 "같지 않은 비교"명령 !=이 없으므로 C # 연산자는 정확히 일치하지 않으며 문자 그대로 번역 할 수 없습니다.

그러나 "비교-동일"명령 ( 연산자 ceq와 직접 대응 ==)이 있으므로 일반적으로 x != y약간 더 긴 것으로 번역 (x == y) == false됩니다.

또한 는 "비교 -보다 큼"IL (의 명령 cgt) 컴파일러가 널 (null)에 대한 객체의 불평등 비교되는 (즉, 짧은 IL 코드를 생성)를 특정 단축키를 활용할 수 있습니다, obj != null그들은 것처럼, "번역 취득 obj > null".

좀 더 자세히 살펴 보겠습니다.

IL에 "같지 않은 비교"명령이 없으면 컴파일러가 다음 방법을 어떻게 변환합니까?

static bool IsNotEqual(int x, int y)
{
    return x != y;
}

위에서 이미 언급했듯이 컴파일러는 다음으로 x != y바꿉니다 (x == y) == false.

.method private hidebysig static bool IsNotEqual(int32 x, int32 y) cil managed 
{
    ldarg.0   // x
    ldarg.1   // y
    ceq
    ldc.i4.0  // false
    ceq       // (note: two comparisons in total)
    ret
}

컴파일러가 항상 상당히 오래 걸리는 패턴을 생성하는 것은 아닙니다. y상수 0으로 바꾸면 어떻게되는지 보자 :

static bool IsNotZero(int x)
{
    return x != 0;
}

생성 된 IL은 일반적인 경우보다 다소 짧습니다.

.method private hidebysig static bool IsNotZero(int32 x) cil managed 
{
    ldarg.0    // x
    ldc.i4.0   // 0
    cgt.un     // (note: just one comparison)
    ret
}

컴파일러는 부호있는 정수가 2의 보수 (결과 비트 패턴이 부호없는 정수로 해석되는 경우- .un평균-0이 가능한 최소값을 갖는 경우)에 저장된다는 사실을 이용할 수 있으므로 x == 0마치 마치 unchecked((uint)x) > 0.

컴파일러는 불평등 검사에 대해 동일한 작업을 수행 할 수 있습니다 null.

static bool IsNotNull(object obj)
{
    return obj != null;
}

컴파일러는 다음과 거의 동일한 IL을 생성합니다 IsNotZero.

.method private hidebysig static bool IsNotNull(object obj) cil managed 
{
    ldarg.0
    ldnull   // (note: this is the only difference)
    cgt.un
    ret
}

분명히, 컴파일러는 null참조의 비트 패턴이 모든 객체 참조에 가능한 가장 작은 비트 패턴 이라고 가정 할 수 있습니다.

이 바로 가기는 공통 언어 인프라 주석 표준 (2003 년 10 월 1 판) (491 페이지의 표 6-4, "이진 비교 또는 분기 작업"의 각주 )에 명시 적으로 언급되어 있습니다 .

" cgt.un는 ObjectRefs (O)에서 허용되고 검증 가능합니다. 이것은 ObjectRef를 null과 비교할 때 일반적으로 사용됩니다 ("같지 않은 비교 "명령이없는 경우가 더 분명합니다)."


3
탁월한 답변, 단 하나의 니트 : 2의 보수는 여기에 관련이 없습니다. 부호있는 정수는 int범위의 음수가 아닌 값 이와 동일한 방식으로 표시되는 방식 int으로 저장됩니다 uint. 그것은 2의 보수보다 훨씬 약한 요구 사항입니다.

3
부호없는 유형에는 음수가 없습니다. 따라서 0과 비교하는 비교 연산은 0이 아닌 숫자를 0보다 작게 처리 할 수 ​​없습니다. 음이 아닌 int값에 uint해당하는 모든 표현은 에서 동일한 값으로 이미 취해 졌으므로 음의 값에 해당하는 모든 표현 은 보다 큰 일부 값에 int해당해야 하지만 실제로 어떤 값이 중요하지는 않습니다. 입니다. (실제로, 실제로 필요한 것은 0 과 모두 동일한 방식으로 표시된다는 것입니다 .)uint0x7FFFFFFFintuint

3
@ hvd : 설명해 주셔서 감사합니다. 당신이 옳습니다. 중요한 것은 두 가지 보완이 아닙니다. 요구 사항입니다 당신이 언급 한 것을 그리고 사실 그 cgt.un취급합니다 intAS를 uint기본 비트 패턴을 변경하지 않고. ( cgt.un모든 음수를 0으로 매핑하여 언더 플로를 수정하려고 시도 한다고 상상해보십시오 .이 경우 > 0!= 0
분명히을

2
객체 참조를 사용하는 객체 참조와 다른 것을 비교하는 >것은 검증 가능한 IL 이라는 것이 놀랍습니다 . 그렇게하면 null이 아닌 두 객체를 비교하고 부울 결과 (비 결정적)를 얻을 수 있습니다. 이는 메모리 안전 문제가 아니지만 안전한 관리 코드의 일반적인 정신에 맞지 않는 부정확 한 디자인처럼 느껴집니다. 이 디자인은 객체 참조가 포인터로 구현된다는 사실을 누설합니다. .NET CLI의 설계 결함 인 것 같습니다.
usr

3
@ usr : 물론입니다! 의 섹션 III.1.1.4 CLI 표준 말한다 "개체 참조 (O 형)이 완전히 불투명하다" 것을 "작업이 허용 된 유일한 비교 평등과 불평등은 ...." 객체 참조가되어 아마도 때문에 없습니다 메모리 주소의 관점에서 정의 된 표준은 개념적으로 (예를 들면의 정의를 참조 0에서 떨어져 널 참조를 유지하기 위해 처리합니다 ldnull, initobj하고 newobj). 따라서 cgt.un객체 참조와 null 참조를 비교 하는 것은 III.1.1.4 섹션과 여러 가지 방법으로 모순되는 것으로 보입니다.
stakx-더 이상
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.