Вычитание 64-битного указателя, потеря значимости целого числа со знаком и возможная ошибка компилятора?

Я недавно вырвался из головы, отлаживая этот фрагмент кода (слегка измененный для простоты представления):

char *packedData;
unsigned char* indexBegin, *indexEnd;
int block, row;

// +------ bad! 
// v
  int cRow = std::upper_bound( indexBegin, indexEnd, row&255 ) - indexBegin - 1;

char value = *(packedData + (block + cRow) * bytesPerRow);

Конечно, присвоение разности двух указателей (результат std :: upper_bound минус начало искомого массива) на int, а не на ptrdiff_t, неверно в 64-битной среде, но возникшее в результате плохое поведение было очень неожиданным. Я ожидал, что это не удастся, когда массив в [indexBegin, indexEnd) был размером более 2 ГБ, так что разница превышала 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-битные значения со знаком, расширяя их знаком до 64 -битные регистры перед их вычитанием и умножением результата на другое 32-битное значение с расширенным знаком, а затем индексирует другой массив с 64-битным результатом этого вычисления. Как бы я ни старался, я не могу понять, согласно какой теории это могло быть правильным.Если бы указатели были вычтены как 64-битные значения, или была бы другая инструкция сразу после imul, эта знаковая расширенная edx в rdx (или последняя версия mov ссылалась на rax + edx, но я не думаю, что это доступно в x86-64), все было бы хорошо (номинально опасно, но я знаю, что [indexBegin, indexEnd) никогда не достигнет длины даже 2 ГБ).

Вопрос носит несколько академический характер, поскольку моя фактическая ошибка легко исправляется простым использованием 64-битного типа для хранения разницы указателей, но является ли это ошибкой компилятора или есть какая-то неясная часть спецификации языка, которая позволяет компилятор, чтобы предположить, что операнды вычитания индивидуально вписываются в тип результата?

EDIT : единственная ситуация, о которой я могу думать, может сделать то, что компилятор сделал правильно, - это если ему разрешено предположить, что целое число недополнение никогда не произойдет (так что, если я вычту два числа и присвою результат знаковое int , компилятор сможет фактически использовать более крупный целочисленный тип со знаком, что в данном случае окажется неверным. ). Разрешено ли это спецификацией языка?

6
задан Jonathan Tomer 10 March 2011 в 19:09
поделиться