[c] Win32에서 unsigned int 로의 이중 캐스트가 2,147,483,648로 잘립니다.

다음 코드를 컴파일합니다.

출력 (MSVC x86) :

INT_MAX: 2147483647
UINT_MAX: 4294967295
Double value: 2147483649.000000
Direct cast value: 2147483648
Indirect cast value: 2147483649

출력 (MSVC x64) :

INT_MAX: 2147483647
UINT_MAX: 4294967295
Double value: 2147483649.000000
Direct cast value: 2147483649
Indirect cast value: 2147483649

에서 Microsoft 설명서 에서 전환으로 최대 값의 정수 서명에 대한 언급이없는 double하려면 unsigned int.

위의 모든 값 은 함수가 반환 될 때 INT_MAX잘립니다 2147483648.

내가 사용하고 비주얼 스튜디오 2019 프로그램을 구축 할 수 있습니다. 이것은 gcc 에서는 발생하지 않습니다 .

내가 뭔가 잘못하고 있습니까? 로 변환하는 안전한 방법 doubleunsigned int있습니까?



답변

컴파일러 버그 …

@anastaciu에서 제공하는 어셈블리에서 직접 캐스트 코드는을 호출하는데 __ftol2_sse, 이는 숫자를 부호있는 long 으로 변환하는 것 같습니다 . 루틴 이름은 ftol2_sse이것이 sse 사용 가능 기계이기 때문입니다. 그러나 float는 x87 부동 소수점 레지스터에 있습니다.

; Line 17
    call    _getDouble
    call    __ftol2_sse
    push    eax
    push    OFFSET ??_C@_0BH@GDLBDFEH@Direct?5cast?5value?3?5?$CFu?6@
    call    _printf
    add esp, 8

반면 간접 캐스트는

; Line 18
    call    _getDouble
    fstp    QWORD PTR _d$[ebp]
; Line 19
    movsd   xmm0, QWORD PTR _d$[ebp]
    call    __dtoui3
    push    eax
    push    OFFSET ??_C@_0BJ@HCKMOBHF@Indirect?5cast?5value?3?5?$CFu?6@
    call    _printf
    add esp, 8

이중 값을 팝하고 로컬 변수에 저장 한 다음 SSE 레지스터에로드하고 __dtoui3부호없는 int 변환 루틴에 대한 double을 호출합니다 .

직접 캐스트의 동작이 C89를 따르지 않습니다. 또한 이후 개정판을 따르지 않습니다. C89 조차도 다음과 같이 명시 적으로 말합니다.

정수 유형의 값이 부호없는 유형으로 변환 될 때 수행되는 나머지 연산은 부동 유형의 값이 부호없는 유형으로 변환 될 때 수행 될 필요가 없습니다. 따라서 이식 가능한 값의 범위는 [0, Utype_MAX + 1) 입니다.


나는 문제가 2005 년부터 계속 될 수 있다고 생각한다. 예전 에는 __ftol2이 코드에서 작동했을 라는 변환 함수 가 있었다. 즉, 값을 부호있는 숫자 -2147483647 로 변환 하여 올바른 값을 생성했을 것이다. 부호없는 숫자로 해석 될 때의 결과.

안타깝게도을 (를 __ftol2_sse) 드롭 인 대체 __ftol2할 수 없습니다.-그저 최하위 값 비트를 그대로 사용하는 대신 LONG_MIN/ 를 반환하여 범위를 벗어난 오류 0x80000000를 알리기 때문에 여기에서는 부호없는 길이로 해석되지 않습니다. 기대했던 모든 것. double a value> to의 변환 은 정의되지 않은 동작 을 가지므로 의 동작 __ftol2_sse은에 대해 유효합니다 .signed longLONG_MAXsigned long


답변

@AnttiHaapala의 답변에 따라 최적화를 사용하여 코드를 테스트 한 /Ox결과 __ftol2_sse더 이상 사용되지 않는 버그가 제거된다는 것을 알았습니다 .

최적화는 인라인 getdouble()되고 상수 표현식 평가를 추가하여 런타임에 변환 할 필요가 없으므로 버그가 사라집니다.

호기심으로 인해 런타임에 float-int 변환을 강제하도록 코드를 변경하는 등 몇 가지 테스트를 수행했습니다. 이 경우 결과는 여전히 정확하며 컴파일러는 최적화 __dtoui3를 통해 두 변환 모두 에서 사용합니다 .

그러나 인라인을 방지 __declspec(noinline) double getDouble(){...}하면 버그가 다시 발생합니다.

__ftol2_sse2147483648두 상황 모두 에서 출력 을 만드는 두 변환 모두에서 호출되며 @zwol 의혹 이 정확했습니다.


컴파일 세부 사항 :

  • 명령 줄 사용 :
cl /permissive- /GS /analyze- /W3 /Gm- /Ox /sdl /D "WIN32" program.c
  • Visual Studio에서 :

    • 비활성화 RTCProject -> Properties -> Code Generation와 설정 기본 런타임 검사기본 .

    • 에서 최적화를 활성화 Project -> Properties -> Optimization하고 최적화/ Ox로 설정합니다 .

    • 디버거 x86모드에서.


답변

아무도 MS의 asm을 보지 않았습니다 __ftol2_sse.

그 결과, x87에서 부호있는 int/ long(Windows의 경우 32 비트 유형 모두)로 안전하게 변환되는 대신 uint32_t.

x86 FP-> 정수 결과를 오버플로하는 정수 명령어는 줄 바꿈 / 자르기 만하 는 것이 아닙니다. 목적지에서 정확한 값을 표현할 수없는 경우 인텔이 “무한 정수”라고 부르는 것을 생성 합니다. 높은 비트 세트, 다른 비트는 지워집니다. 즉0x80000000 .

(또는 FP 유효하지 않은 예외가 마스킹되지 않으면 실행되고 값이 저장되지 않습니다. 그러나 기본 FP 환경에서는 모든 FP 예외가 마스킹됩니다. 이것이 FP 계산의 경우 오류 대신 NaN을 얻을 수있는 이유입니다.)

여기에는 fistp(현재 반올림 모드 사용) 같은 x87 명령어와 같은 SSE2 명령어 cvttsd2si eax, xmm0(0으로 자르기 사용, 이것이 추가 t의미)가 모두 포함됩니다.

따라서 컴파일 double-> unsigned를 호출로 변환 하는 것은 버그 __ftol2_sse입니다.


부주 / 접선 :

x86-64에서는 FP-> uint32_t를로 컴파일하여 cvttsd2si rax, xmm064 비트 부호있는 대상으로 변환하여 정수 대상의 하반부 (EAX)에서 원하는 uint32_t를 생성 할 수 있습니다.

결과가 0..2 ^ 32-1 범위를 벗어나면 C 및 C ++ UB이므로 큰 양의 값이나 음의 값이 정수의 비트 패턴에서 RAX (EAX)의 하위 절반을 0으로 남겨도 괜찮습니다. (정수-> 정수 변환과 달리 값의 모듈로 축소는 보장 되지 않습니다 . 음의 double을 부호없는 정수로 C 표준에 정의 된 int로 캐스트하는 동작입니까? ARM 대 x86에서 다른 동작입니다 . 명확하게 말하면 질문에 아무것도 없습니다. 정의되지 않았거나 구현 정의 된 동작입니다. FP-> int64_t가 있으면이를 사용하여 FP-> uint32_t를 효율적으로 구현할 수 있습니다. 여기에는 x87이 포함됩니다.fistp 64 비트 모드에서 64 비트 정수만 직접 처리 할 수있는 SSE2 명령어와 달리 32 비트 및 16 비트 모드에서도 64 비트 정수 대상을 쓸 수 있습니다.


답변