64 비트 포인터 뺄셈, 부호있는 정수 언더 플로 및 가능한 컴파일러 버그?
물론 두 포인터의 차이를 지정하면 (std::upper_bound
에서 검색된 배열의 시작 부분을 뺀 값) ptrdiff_t가 아닌 int로 변환하는 것은 64 비트 환경에서는 잘못이지만, 결과적으로 발생하는 특별한 나쁜 동작은 매우 예상치 못한 결과였습니다. 나는 [indexBegin, indexEnd]의 배열이 2GB보다 크면 그 차이가 int를 오버플로 할 때 실패 할 것으로 예상된다. 실제로 일어난 것은 indexBegin 및 indexEnd가 2^31의 양면에 값 (즉, indexBegin = 0x7fffffe0, indexEnd = 0x80000010)을 가진 경우의 충돌이었습니다. 추가 조사를 통해 다음과 같은 x86-64 어셈블리 코드가 생성되었습니다 (MSVC++ 2005에서 최적화에 의해 생성됨).
; (inlined code of std::upper_bound, which leaves indexBegin in rbx,
; the result of upper_bound in r9, block at *(r12+0x28), and data at
; *(r12+0x40), immediately precedes this point)
movsxd rcx, r9d ; movsxd?!
movsxd rax, ebx ; movsxd?!
sub rcx, rax
lea rdx, [rcx+rdi-1]
movsxd rax, dword ptr [r12+28h]
imul rdx, rax
mov rax, qword ptr [r12+40h]
mov rcx, byte ptr[rdx+rax]
이 코드는 을 부호있는 32 비트 값으로 뺄셈되는 포인터 x 64로 부호 확장합니다 비트 레지스터를 빼고 그 결과를 다른 부호 확장 32 비트 값으로 곱한 다음 해당 계산의 64 비트 결과로 다른 배열을 인덱싱합니다. 내가 할 수도있는 것처럼 시도해 보라. 나는 이것이 어떤 이론 하에서 올바른지 알 수 없다. 포인터가 64 비트 값으로 감산되었거나 imul 바로 다음에 edx가 rdx로 확장 된 (또는 최종 mov 참조 된 rax + edx가 있었지만 다른 명령을 사용할 수 있다고 생각하지 않습니다. x86-64), 모든 것이 괜찮을 것입니다 (명목상 위험하지만, [indexBegin, indexEnd]가 2GB에 근접하지 않음을 알고 있습니다).
질문은 다소 학문적입니다. 실제 버그는 포인터 차이를 잡기 위해 64 비트 형식을 사용하는 것만으로 쉽게 해결할 수 있지만 컴파일러 버그이거나 허용 할 수없는 언어 사양의 일부입니다 컴파일러는 뺄셈의 피연산자가 결과 유형에 개별적으로 적합하다고 가정해야합니까?
EDIT : 컴파일러가 수행 한 작업이 괜찮을 것이라고 생각할 수있는 유일한 상황은 정수 언더 플로가 발생하지 않는다고 가정 할 수 있는지의 여부입니다 (따라서 두 숫자를 빼고 결과를 a signed int
이면 컴파일러는 더 큰 부호있는 정수 타입을 실제로 사용할 수 있습니다.이 경우에는 틀린 것으로 나타납니다). 언어 사양에서 허용됩니까?
을 당신이 합법적 CODEGEN 있다고 생각 여기 버그. VS2010에서 여전히 발생하는 것 같습니다. 당신은 http://connect.microsoft.com에 이러한 세부 사항을 게시해야합니다 –