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);

もちろん、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、コンパイラは次のようになります。より大きな符号付き整数型を実際に自由に使用できますが、この場合は間違っていることがわかります)。それは言語仕様で許可されていますか?

4

2 に答える 2

1

ポインタから非ブール型へのC++変換は、次のようになります。

  1. ポインタと同じサイズの符号なし整数に変換します
  2. 符号なし整数から宛先タイプ(この場合は整数)に変換します

これで、コンパイラーは整数の減算を認識します。サインを保持している限り、適切と思われる方法でこれを実行するのは自由です。そのため、Visual-C ++は、64ビットレジスタを使用してこれを実行することを決定しました。

左辺値に割り当てる前に、右側をunsigned intにキャストすることで、この操作順序を確認できます。これはあなたが期待していた悪い振る舞いをもたらすでしょう。

于 2011-03-09T21:26:03.847 に答える
1

少し遅れましたが、最後の編集後に質問に答えられなかったので見てください。

はい、オーバーフローは未定義動作です。そして、はい、UBは直感的でない影響を与える可能性があります。特に、UBはすでに実行されたコードに影響を与えるように見える場合があります。

実際の結果は、コンパイラがオーバーフローがないことを前提として動作できることです。古典的な例はif (x+1<x)、コンパイラが。で置き換えることができ、実際に置き換えることができるオーバーフローの誤ったテストですif (false)

もちろん、32ビット変数が実際に64ビットレジスタに格納されている場合、非常に紛らわしい「オーバーフロー」動作が発生する可能性があるため、オーバーフローに使用できるスペースがあります。そのレジスタは値を保持できます1<<32。これは、未定義動作のC ++プログラムの結果について賢明に推論できないことを示しています。つまりint、値MAX_INT+1(!)を持つaが効果的にあります。

于 2017-09-21T14:05:27.400 に答える