Double의 "=="연산자 정의


126

어떤 이유로 나는 클래스의 .NET Framework 소스를 몰래 들여 Double오고 선언 ==이 다음 과 같은 것을 알았습니다 .

public static bool operator ==(Double left, Double right) {
    return left == right;
}

모든 연산자에 동일한 논리가 적용됩니다 .


  • 그러한 정의의 요점은 무엇입니까?
  • 어떻게 작동합니까?
  • 왜 무한 재귀를 일으키지 않습니까?

17
나는 끝없는 재귀를 기대합니다.
HimBromBeere

5
나는 그것이 이중으로 어디서나 비교를 위해 사용되지 않고, 대신 ceqIL로 발행 된다는 것을 확신합니다 . 이것은 일부 문서 목적을 채우기 위해 거기에 있지만 소스를 찾을 수는 없습니다.
Habib

2
이 연산자는 리플렉션을 통해 얻을 수 있습니다.
Damien_The_Unbeliever

3
그것은 결코 호출되지 않을 것입니다. 컴파일러는 동등 논리가 구워졌습니다 (ceq opcode) Double의 == 연산자는 언제 호출됩니까?를
Alex K.

1
double을 0으로 나누는 @ZoharPeled는 유효하며 양수 또는 음의 무한대가됩니다.
Magnus

답변:


62

실제로 컴파일러는 ==연산자를 ceqIL 코드로 바꾸며 언급 한 연산자는 호출되지 않습니다.

소스 코드에서 연산자의 이유는 C # 이외의 언어에서 CEQ직접 호출로 (또는 리플렉션을 통해) 호출 하지 않는 언어에서 호출 할 수 있습니다 . 연산자 내의 코드 로 컴파일 CEQ되므로 무한 재귀는 없습니다.

실제로 리플렉션을 통해 연산자를 호출하면 연산자가 CEQ명령이 아닌 호출 되고 프로그램이 예상대로 종료되므로 무한히 재귀하지 않는 것을 알 수 있습니다 .

double d1 = 1.1;
double d2 = 2.2;

MethodInfo mi = typeof(Double).GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public );

bool b = (bool)(mi.Invoke(null, new object[] {d1,d2}));

결과 IL (LinqPad 4에 의해 컴파일 됨) :

IL_0000:  nop         
IL_0001:  ldc.r8      9A 99 99 99 99 99 F1 3F 
IL_000A:  stloc.0     // d1
IL_000B:  ldc.r8      9A 99 99 99 99 99 01 40 
IL_0014:  stloc.1     // d2
IL_0015:  ldtoken     System.Double
IL_001A:  call        System.Type.GetTypeFromHandle
IL_001F:  ldstr       "op_Equality"
IL_0024:  ldc.i4.s    18 
IL_0026:  call        System.Type.GetMethod
IL_002B:  stloc.2     // mi
IL_002C:  ldloc.2     // mi
IL_002D:  ldnull      
IL_002E:  ldc.i4.2    
IL_002F:  newarr      System.Object
IL_0034:  stloc.s     04 // CS$0$0000
IL_0036:  ldloc.s     04 // CS$0$0000
IL_0038:  ldc.i4.0    
IL_0039:  ldloc.0     // d1
IL_003A:  box         System.Double
IL_003F:  stelem.ref  
IL_0040:  ldloc.s     04 // CS$0$0000
IL_0042:  ldc.i4.1    
IL_0043:  ldloc.1     // d2
IL_0044:  box         System.Double
IL_0049:  stelem.ref  
IL_004A:  ldloc.s     04 // CS$0$0000
IL_004C:  callvirt    System.Reflection.MethodBase.Invoke
IL_0051:  unbox.any   System.Boolean
IL_0056:  stloc.3     // b
IL_0057:  ret 

흥미롭게도 - 동일 사업자가 통합 유형 (중 반사 참조 소스 또는 통해) 존재하지 않습니다 만 Single, Double, Decimal, String, 그리고 DateTime, 그들은 다른 언어에서 호출 할 존재 내 이론을 반증한다. 분명히이 연산자없이 다른 언어에서 두 정수를 동일하게 사용할 수 있으므로 "왜 그것들이 존재 double합니까? "라는 질문으로 되돌아갑니다 .


12
내가 볼 수있는 유일한 문제는 C # 언어 사양에 과부하 된 연산자가 내장 연산자보다 우선한다는 것입니다. 따라서 적합한 C # 컴파일러는 여기에서 오버로드 된 연산자를 사용할 수 있으며 무한 재귀를 생성해야합니다. 흠. 문제.
Damien_The_Unbeliever

5
그것은 질문에 답하지 않습니다, imho. 코드가 번역 된 내용 만 설명하고 이유는 설명하지 않습니다. 섹션 7.3.4 C # 언어 사양의 이진 연산자 과부하 해결 에 따르면 무한 재귀가 예상됩니다. 참조 소스 ( referencesource.microsoft.com/#mscorlib/system/… )는 실제로 여기에 적용되지 않는다고 가정합니다 .
Dirk Vollmar

6
@ DStanley-나는 무엇이 생산되는지 부정하지 않습니다. 언어 사양과 조정할 수 없다고 말하고 있습니다. 그게 문제입니다. 나는 Roslyn을 통해 구멍을 뚫는 것에 대해 생각하고 있었고 여기에서 특별한 처리를 찾을 수 있는지를 알았지 만 현재는 이것을 잘못 설정했습니다 (잘못된 기계)
Damien_The_Unbeliever

1
@Damien_The_Unbeliever 그렇기 때문에 스펙에 예외가 있거나 "내장"연산자에 대한 다른 해석이라고 생각합니다.
D Stanley

1
@ Jon Skeet이 아직 답변하거나 이에 대해 언급하지 않았으므로 버그 (예 : 사양 위반)라고 생각합니다.
TheBlastOne

37

여기서 가장 혼란스러운 점은 모든 .NET 라이브러리 (이 경우 BCL의 일부 가 아닌 확장 숫자 라이브러리 )가 표준 C #으로 작성 되었다고 가정한다는 것 입니다. 항상 그런 것은 아니며 언어마다 규칙이 다릅니다.

표준 C #에서 사용자가보고있는 코드 조각은 연산자 오버로드 확인 작동 방식으로 인해 스택 오버플로가 발생합니다. 그러나 코드는 실제로 표준 C #에 있지 않습니다. 기본적으로 C # 컴파일러의 문서화되지 않은 기능을 사용합니다. 연산자를 호출하는 대신 다음 코드를 생성합니다.

ldarg.0
ldarg.1
ceq
ret

그게 다야 :) 100 % 동등한 C # 코드는 없습니다. C #에서는 자체 유형 이 불가능합니다 .

그럼에도 불구하고 실제 연산자는 C # 코드를 컴파일 할 때 사용되지 않습니다. 컴파일러는이 경우와 같이 op_Equality호출을 단순한로 대체하는 많은 최적화를 수행합니다 ceq. 다시 말하지만, 자신의 DoubleEx구조체 에서 이것을 복제 할 수 없습니다 -컴파일러 마술입니다.

이것은 분명히 .NET의 독특한 상황은 아닙니다. 표준 C #에는 유효하지 않은 코드가 많이 있습니다. 그 이유는 일반적으로 (a) 컴파일러 해킹과 (b) 이상한 (c) 런타임 해킹과 함께 다른 언어입니다 (나는 당신을보고 있습니다 Nullable!).

Roslyn C # 컴파일러는 oepn 소스이므로 실제로 과부하 해결이 결정되는 위치를 알려줄 수 있습니다.

모든 이진 연산자가 해결되는 장소

내장 연산자의 "바로 가기"

바로 가기를 보면 double과 double 사이의 동등성 으로 인해 유형에 정의 된 실제 연산자가 아닌 내장형 이중 연산자가 나타납니다 ==. .NET 형식 시스템은 Double다른 형식과 같은 척해야 하지만 C #은 그렇지 않습니다 .C # double의 기본 요소입니다.


1
참조 소스의 코드가 "역 엔지니어링 됨"에 동의하지 않습니다. 이 코드에는 컴파일러 지시문 #if및 컴파일 된 코드에없는 기타 아티팩트가 있습니다. 또한 double리버스 엔지니어링 된 경우 int또는 리버스 엔지니어링되지 않은 이유는 long무엇입니까? 나는 소스 코드에 대한 이유가 있다고 생각하지만 ==연산자 내부의 사용은 CEQ재귀를 막는 것으로 컴파일 된다고 생각합니다 . 연산자는 해당 유형에 대해 "사전 정의 된"연산자이므로 재정의 할 수 없으므로 오버로드 규칙이 적용되지 않습니다.
D Stanley

@ DStanley 모든 코드가 리버스 엔지니어링 되었음을 암시하고 싶지 않았습니다 . 그리고 다시, doubleBCL의 일부가 아닙니다-그것은 별도의 라이브러리에 있으며 C # 사양에 포함됩니다. 예,는 ==에 컴파일 ceq되지만 여전히 코드에서 복제 할 수없는 컴파일러 핵이며 C # 사양의 일부가 아닌 무언가입니다 ( 구조 의 float64필드 와 마찬가지로 Double). C #의 계약 부분이 아니므로 C # 컴파일러로 컴파일 된 경우에도 C #을 유효한 C #처럼 취급 할 필요가 없습니다.
Luaan

@DStanely 실제 프레임 워크가 어떻게 구성되어 있는지 찾을 수 없었지만 .NET 2.0의 참조 구현에서 까다로운 부분은 모두 컴파일러 내장 함수이며 C ++로 구현되었습니다. 물론 여전히 많은 .NET 네이티브 코드가 있지만 "2 개의 더블 비교"와 같은 것은 순수한 .NET에서 제대로 작동하지 않습니다. 이것이 부동 소수점 숫자가 BCL에 포함되지 않은 이유 중 하나입니다. 말했다 즉, 코드가되어 (비표준) C #으로 구현, 아마 정확하게 당신이 앞에서 언급 한 이유 - 진짜 .NET 형식으로 그 유형을 취급 할 수 있는지 다른 .NET 컴파일러를 만들기 위해.
Luaan

@DStanley 그러나 괜찮아요. "역 엔지니어링 된"참조를 제거하고 C #이 아니라 "표준 C #"을 명시 적으로 언급 한 답변을 다시 작성했습니다. 그리고 취급하지 않는 double것과 같은 방식으로 intlong- int하고 long있다는 원시적 종류 모든 닷넷 언어를 지원해야합니다. float, decimaldouble아니다.
루안

12

기본 유형의 소스는 혼란 스러울 수 있습니다. Double구조체의 첫 번째 줄을 보셨습니까 ?

일반적으로 다음과 같이 재귀 구조체를 정의 할 수 없습니다.

public struct Double : IComparable, IFormattable, IConvertible
        , IComparable<Double>, IEquatable<Double>
{
    internal double m_value; // Self-recursion with endless loop?
    // ...
}

기본 유형은 CIL에서 기본적으로 지원됩니다. 일반적으로 객체 지향 유형처럼 취급되지 않습니다. float64CIL에서 와 같이 사용되는 경우 double은 64 비트 값 입니다. 그러나 일반적인 .NET 유형으로 처리되는 경우 실제 값을 포함하고 다른 유형과 같은 메소드를 포함합니다.

여기 보시는 것은 운영자에게도 같은 상황입니다. 일반적으로 이중 유형 유형을 직접 사용하면 호출되지 않습니다. BTW, 소스는 CIL에서 다음과 같습니다.

.method public hidebysig specialname static bool op_Equality(float64 left, float64 right) cil managed
{
    .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor()
    .custom instance void __DynamicallyInvokableAttribute::.ctor()
    .maxstack 8
    L_0000: ldarg.0
    L_0001: ldarg.1
    L_0002: ceq
    L_0004: ret
}

보시다시피, 무한 루프가 없습니다 ( ceq을 호출하는 대신 계측기가 사용됨 System.Double::op_Equality). 따라서 double이 객체처럼 취급되면 연산자 메소드가 호출되어 결국 float64CIL 수준 에서 기본 유형 으로 처리 됩니다.


1
이 게시물의 첫 부분을 이해하지 못하는 사람들을 위해 (일반적으로 자체 값 유형을 쓰지 않기 때문에) 코드를 사용해보십시오 public struct MyNumber { internal MyNumber m_value; }. 물론 컴파일 할 수 없습니다. 오류는 오류 CS0523 : 구조체의 멤버 'MyNumber'는 구조체 레이아웃주기가 발생 유형 'MyNumber.m_value'
Jeppe의 Stig 닐슨

8

JustDecompile 을 사용하여 CIL 을 살펴 보았습니다. 내부 ==는 CIL ceq op 코드로 변환됩니다 . 다시 말해, 기본 CLR 평등입니다.

두 개의 이중 값을 비교할 때 C # 컴파일러가 참조 연산자 ceq인지 ==연산자 인지 궁금 합니다. 사소한 예에서 나는 (아래)를 생각해 냈습니다 ceq.

이 프로그램:

void Main()
{
    double x = 1;
    double y = 2;

    if (x == y)
        Console.WriteLine("Something bad happened!");
    else
        Console.WriteLine("All is right with the world");
}

다음 CIL을 생성합니다 (label이있는 명령문에주의하십시오 IL_0017).

IL_0000:  nop
IL_0001:  ldc.r8      00 00 00 00 00 00 F0 3F
IL_000A:  stloc.0     // x
IL_000B:  ldc.r8      00 00 00 00 00 00 00 40
IL_0014:  stloc.1     // y
IL_0015:  ldloc.0     // x
IL_0016:  ldloc.1     // y
IL_0017:  ceq
IL_0019:  stloc.2
IL_001A:  ldloc.2
IL_001B:  brfalse.s   IL_002A
IL_001D:  ldstr       "Something bad happened!"
IL_0022:  call        System.Console.WriteLine
IL_0027:  nop
IL_0028:  br.s        IL_0035
IL_002A:  ldstr       "All is right with the world"
IL_002F:  call        System.Console.WriteLine
IL_0034:  nop
IL_0035:  ret

-2

System.Runtime.Versioning 네임 스페이스에 대한 Microsoft 설명서에 표시된대로이 네임 스페이스에있는 형식은 사용자 응용 프로그램이 아닌 .NET Framework 내에서 사용하도록되어 있습니다 .System.Runtime.Versioning 네임 스페이스에는 버전 관리를 지원하는 고급 형식이 포함되어 있습니다. .NET Framework의 단계별 구현.


무엇 System.Runtime.Versioning과 관련이 System.Double있습니까?
Koopakiller
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.