私は最近、このコードのデバッグをやめました(プレゼンテーションを簡単にするために少し変更しました)。
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);
もちろん、2つのポインターの差(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ビット値として扱い、64ビットレジスターに符号拡張してから減算し、結果に別の符号拡張32ビット値を乗算してから、別の配列に64でインデックスを付けます。その計算のビット結果。頑張ってみてください。これがどのような理論で正しいのか理解できません。ポインターが64ビット値として減算された場合、またはimulの直後に、edxをrdxに符号拡張する別の命令があった場合(または最後のmovがrax + edxを参照した場合、それはx86-64)、すべてが正常です(名目上危険ですが、[indexBegin、indexEnd)の長さが2GBに近づくことは決してないことを私は知っています)。
私の実際のバグは、ポインターの違いを保持するために64ビット型を使用するだけで簡単に修正できるため、質問はやや学術的ですが、これはコンパイラーのバグですか、それともコンパイラーが想定できる言語仕様のあいまいな部分がありますか?減算のオペランドが個別に結果型に適合することは?
編集:私が考えることができる唯一の状況は、コンパイラが大丈夫だと思うことです。整数のアンダーフローが決して起こらないと仮定できる場合です(したがって、2つの数値を減算して結果をに割り当てるとsigned int
、コンパイラは次のようになります。より大きな符号付き整数型を実際に自由に使用できますが、この場合は間違っていることがわかります)。それは言語仕様で許可されていますか?