6

我最近在调试这段代码时费尽心思(为了简单起见,稍作修改):

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) 的数组大小超过 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 位值,在减去它们并将结果乘以另一个带符号扩展的 32 位值之前,将它们符号扩展为 64 位寄存器,然后用 64- 索引另一个数组该计算的位结果。尽我所能,我无法弄清楚在什么理论下这可能是正确的。如果将指针作为 64 位值减去,或者在 imul 之后是否有另一条指令,将 edx 符号扩展为 rdx(或者最终的 mov 引用了 rax+edx,但我认为这在x86-64),一切都会好起来的(名义上很危险,但我碰巧知道 [indexBegin, indexEnd) 的长度甚至永远不会接近 2GB)。

这个问题有点学术性,因为我的实际错误很容易通过仅使用 64 位类型来保存指针差异来修复,但这是编译器错误,还是语言规范中有一些模糊的部分允许编译器假设减法的操作数将分别适合结果类型吗?

编辑:我能想到的唯一情况会使编译器做得很好,如果允许假设整数下溢永远不会发生(这样如果我减去两个数字并将结果分配给 a signed int,编译器将是可以自由地实际使用更大的有符号整数类型,在这种情况下被证明是错误的)。语言规范允许这样做吗?

4

2 回答 2

1

从指针到非布尔类型的 C++ 转换如下所示:

  1. 转换为与指针大小相等的无符号整数
  2. 从无符号整数转换为目标类型(在您的情况下为整数)

现在,编译器看到整数减法。只要它保留标志,它可以自由地以它认为合适的任何方式执行此操作。因此,Visual-C++ 决定使用 64 位寄存器来执行此操作。

您可以通过在分配给您的左值之前将右手边转换为 unsigned int 来验证此操作顺序。这将导致您预期的不良行为。

于 2011-03-09T21:26:03.847 回答
1

有点晚了,但是在最后一次EDIT之后没有回答这个问题。

是的,溢出是未定义的行为。是的,UB 可能会产生不直观的影响。特别是,UB 可能会影响已经执行的代码。

实际结果确实是允许编译器在没有溢出的假设下工作。经典的例子是if (x+1<x),编译器可以并且确实替换为溢出的错误测试if (false)

是的,当您的 32 位变量实际存储在 64 位寄存器中时,您可能会得到相当混乱的“溢出”行为,因此有空间可供溢出。该寄存器可以保存该值1<<32,这表明您无法合理地推断具有未定义行为的 C++ 程序的结果:您实际上拥有一个int带有值的MAX_INT+1(!)

于 2017-09-21T14:05:27.400 回答