16

奇妙なクラッシュが発生しています。そして、それは私のコードのバグなのか、それともコンパイラーのバグなのか疑問に思います。次の C++ コードを Microsoft Visual Studio 2010 で最適化されたリリース ビルドとしてコンパイルすると、マークされた行でクラッシュします。

struct tup { int x; int y; };

class C 
{
public:
  struct tup* p;

  struct tup* operator--() { return --p; }
  struct tup* operator++(int) { return p++; }

  virtual void Reset() { p = 0;}
};

int main ()
{
  C c;
  volatile int x = 0;
  struct tup v1;
  struct tup v2 = {0, x};

  c.p = &v1;
  (*(c++)) = v2;

  struct tup i = (*(--c));   // crash! (dereferencing a NULL-pointer)
  return i.x;
}

逆アセンブルを見ると、クラッシュする必要があることは明らかです。

int _tmain(int argc, _TCHAR* argv[])
{
00CE1000  push        ebp  
00CE1001  mov         ebp,esp  
00CE1003  sub         esp,0Ch  
  C c;
  volatile int x = 0;
00CE1006  xor         eax,eax  
00CE1008  mov         dword ptr [x],eax  
  struct tup v1;
  struct tup v2 = {0, x};
00CE100B  mov         ecx,dword ptr [x]  

  c.p = &v1;
  (*(c++)) = v2;
00CE100E  mov         dword ptr [ebp-8],ecx  

  struct tup i = (*(--c));
00CE1011  mov         ecx,dword ptr [x]  
00CE1014  mov         dword ptr [v1],eax  
00CE1017  mov         eax,dword ptr [ecx]  
00CE1019  mov         ecx,dword ptr [ecx+4]  
00CE101C  mov         dword ptr [ebp-8],ecx  
return i.x;
}
00CE101F  mov         esp,ebp  
00CE1021  pop         ebp  
00CE1022  ret  

オフセット 00CE1008 で、x に 0 を書き込みます。

オフセット 00CE100B で x (0) を ecx に読み込みます

オフセット 00CE1017 で、その 0 ポインターを逆参照します。

考えられる理由は 2 つあります。

  • 私のコードに未定義の動作の微妙な (またはそれほど微妙ではない?) ケースがあり、コンパイラがこの未定義の動作をクラッシュに「最適化」します。

  • またはコンパイラのバグがあります

問題の原因が何かわかりますか?

ありがとうございました、

ジョナス

編集:「無効な場所へのポインター」に関するコメントに対処するには

v1bestruct tup v1[10];と setに変更するとc.p = &v1[0];、無効な場所へのポインターはなくなります。しかし、私はまだ同じ行動を観察できます。逆アセンブルはわずかに異なっているように見えますが、それでもクラッシュがあり、これは 0 を ecx にロードして逆参照することによって引き起こされます。

編集:結論

したがって、おそらくバグです。変更するとクラッシュが消えることがわかりました

struct tup* operator--() { return --p; }

struct tup* operator--() { --p; return p; }

bames53 が教えてくれるように、クラッシュは VS2011 では発生せず、修正されたに違いないと結論付けています。

それにもかかわらず、私は次の 2 つの理由から、そのバグを報告することにしました。

  • バグは VS2011 にまだ存在する可能性があります。私のコードがバグを引き起こさないようにオプティマイザーが変更されたのかもしれません。volative(バグは非常に微妙なようです。またはを削除すると発生しませんvirtual void Reset())

  • 私の回避策がクラッシュを除外するための信頼できる方法であるかどうか、または他の場所でコードを変更するとバグが再発する可能性があるかどうかを知りたいです。

リンクは次のとおりです。

https://connect.microsoft.com/VisualStudio/feedback/details/741628/error-in-code-generation-for-x86

4

3 に答える 3

17

コードは問題ありません。コンパイラのバグです。

コード*(c++) = v2はポストインクリメントc.pして元の値を生成します。その値は前の行で割り当てられており、&v1. したがって、実際にはv1 = v2;、それは完全に問題ありません。

c.pv1は、標準の §5.7p4 に従って、のみを保持する 1 つの要素の配列の最後の 1 つとして動作するようになりました。

これらの演算子 [+および-] の目的上、非配列オブジェクトへのポインターは、要素型としてオブジェクトの型を持つ長さ 1 の配列の最初の要素へのポインターと同じように動作します。

次に*(--c)、そのポインターを元に戻して&v1逆参照しますが、これも問題ありません。

于 2012-05-10T17:54:01.337 に答える
1

UB やコンパイラのバグである必要はありません。VS2010 が作成された方法が原因である可能性はありません。

厳密に言えば、プログラムは明確に定義された動作を示します。ただし、それは最新の C++ 標準にのみ準拠している可能性があります。VS2010 は、この条項が含まれていない可能性があるドラフト標準に対してのみ実装されています。そうでない場合、コードは UB ではありませんが、VS が UB を生成するのは間違いではありません。これらは作成時の要件だったからです。

もちろん、C++03 でスタック オブジェクトを 1 つのオブジェクトの配列として扱うことが合法である場合、それはコンパイラのバグです。

編集:あなたが述べたように配列のクラッシュが引き続き発生する場合、それは間違いなくコンパイラのバグです。

于 2012-05-10T17:39:18.650 に答える
-3

cp を取り&v1、後で演算子 ++ を使用して進めます。スタックの順序に依存できないため、未定義の動作が発生します ( (&v1)+1 != &v2)

于 2012-05-10T17:25:44.857 に答える