[C#] 누구나 C #에서 부호가있는 수레로 이상한 행동을 설명 할 수 있습니까?

주석이있는 예는 다음과 같습니다.

class Program
{
    // first version of structure
    public struct D1
    {
        public double d;
        public int f;
    }

    // during some changes in code then we got D2 from D1
    // Field f type became double while it was int before
    public struct D2
    {
        public double d;
        public double f;
    }

    static void Main(string[] args)
    {
        // Scenario with the first version
        D1 a = new D1();
        D1 b = new D1();
        a.f = b.f = 1;
        a.d = 0.0;
        b.d = -0.0;
        bool r1 = a.Equals(b); // gives true, all is ok

        // The same scenario with the new one
        D2 c = new D2();
        D2 d = new D2();
        c.f = d.f = 1;
        c.d = 0.0;
        d.d = -0.0;
        bool r2 = c.Equals(d); // false! this is not the expected result        
    }
}

그래서 이것에 대해 어떻게 생각하십니까?



답변

버그는 다음 두 줄에 있습니다 System.ValueType: (참조 소스에 들어갔습니다)

if (CanCompareBits(this))
    return FastEqualsCheck(thisObj, obj);

(두 방법 모두 [MethodImpl(MethodImplOptions.InternalCall)])

모든 필드의 너비가 8 바이트 인 경우 CanCompareBits실수로 true를 반환하여 서로 다르지만 의미 적으로 동일한 두 값을 비트 단위로 비교합니다.

하나 이상의 필드가 8 바이트 너비가 아닌 경우 CanCompareBitsfalse를 반환하고 코드는 리플렉션을 사용하여 필드를 반복하고 Equals각 값을 호출 -0.0합니다 0.0.

CanCompareBitsSSCLI 의 소스는 다음과 같습니다 .

FCIMPL1(FC_BOOL_RET, ValueTypeHelper::CanCompareBits, Object* obj)
{
    WRAPPER_CONTRACT;
    STATIC_CONTRACT_SO_TOLERANT;

    _ASSERTE(obj != NULL);
    MethodTable* mt = obj->GetMethodTable();
    FC_RETURN_BOOL(!mt->ContainsPointers() && !mt->IsNotTightlyPacked());
}
FCIMPLEND


답변

http://blogs.msdn.com/xiangfan/archive/2008/09/01/magic-behind-valuetype-equals.aspx 에서 답을 찾았습니다 .

핵심 부분은에 대한 소스 주석으로 CanCompareBits, 스타일 비교 ValueType.Equals사용 여부를 결정하는 데 사용됩니다 memcmp.

CanCompareBits의 의견은 “값 유형에 포인터가 포함되어 있지 않고 압축되어 있으면 true를 반환합니다”라고 말합니다. FastEqualsCheck는 “memcmp”를 사용하여 비교 속도를 높입니다.

저자는 OP에 의해 설명 된 문제를 정확하게 진술합니다.

float 만 포함하는 구조가 있다고 가정하십시오. 하나에 +0.0이 포함되고 다른 하나에 -0.0이 포함되면 어떻게됩니까? 그것들은 동일해야하지만 기본 이진 표현은 다릅니다. Equals 메서드를 재정의하는 다른 구조를 중첩하면 해당 최적화도 실패합니다.


답변

Vilx의 추측은 맞습니다. “CanCompareBits”가 수행하는 작업은 해당 값 유형이 메모리에 “꽉 조여져 있는지”확인하는 것입니다. 꽉 채워진 구조체는 구조를 구성하는 이진 비트를 간단히 비교하여 비교됩니다. 느슨하게 패킹 된 구조는 모든 멤버에서 Equals를 호출하여 비교됩니다.

이것은 SLaks가 두 배인 구조체로 재현한다는 관찰을 설명합니다. 그러한 구조체는 항상 단단히 포장되어 있습니다.

불행히도 여기서 보았 듯이, double의 비트 비교와 double의 Equals 비교가 다른 결과를 나타 내기 때문에 의미상의 차이가 발생합니다.


답변

반 답변 :

리플렉터는 ValueType.Equals()다음과 같은 작업을 수행합니다.

if (CanCompareBits(this))
    return FastEqualsCheck(this, obj);
else
    // Use reflection to step through each member and call .Equals() on each one.

불행히도 CanCompareBits()FastEquals()(정적 메서드 모두) extern ( [MethodImpl(MethodImplOptions.InternalCall)])이며 사용할 수있는 소스가 없습니다.

한 사례를 비트별로 비교할 수있는 이유와 다른 사례를 비교할 수없는 이유로 돌아 가기 (정렬 문제 일 수 있습니까?)


답변

그것은 않습니다 모노의 gmcs 2.4.2.3으로, 나를 위해 진정한 제공합니다.


답변

간단한 테스트 사례 :

Console.WriteLine("Good: " + new Good().Equals(new Good { d = -.0 }));
Console.WriteLine("Bad: " + new Bad().Equals(new Bad { d = -.0 }));

public struct Good {
    public double d;
    public int f;
}

public struct Bad {
    public double d;
}

편집 : 버그도 float와 함께 발생하지만 구조체의 필드가 8 바이트의 배수가 될 때만 발생합니다.


답변

신호 비트 만 0.0다르므로 비트 단위 비교와 관련이 있어야합니다 -0.0.