由于未定义的行为或编译器错误导致 C++ 代码崩溃?

我遇到了奇怪的崩溃.我想知道这是否是我的代码或编译器中的错误.当我使用 Microsoft Visual Studio 2010 编译以下 C++ 代码作为优化版本构建时,它在标记的行中崩溃:

I am experiencing strange crashes. And I wonder whether it is a bug in my code, or the compiler. When I compile the following C++ code with Microsoft Visual Studio 2010 as an optimized release build, it crashes in the marked line:

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

查看反汇编,很明显它必须崩溃:

Looking into the disassembly, it's obvious that it must crash:

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 处,它将 0 写入 x.

At offset 00CE1008 it writes a 0 into x.

在偏移量 00CE100B 处,它将 x(0)读入 ecx

At offset 00CE100B it reads x (the 0) into ecx

在偏移量 00CE1017 处,它取消引用该 0 指针.

At offset 00CE1017 it dereferences that 0-pointer.

我看到两个可能的原因:

I see two possible reasons:

  • 我的代码中是否存在一些微妙(或不那么微妙?)未定义行为的情况并且编译器将这种未定义的行为优化"为崩溃.

  • Either there is some subtle (or not so subtle?) case of undefined behaviour in my code and the compiler "optimizes" this undefined behaviour into a crash.

或者存在编译器错误

有谁知道可能导致问题的原因吗?

Does anyone see what might cause the problem?

谢谢,

乔纳斯

解决有关指向无效位置的指针"的评论

如果我将 v1 更改为 struct tup v1[10]; 并设置 cp = &v1[0];,那么不会有指向无效位置的指针.但我仍然可以观察到相同的行为.反汇编看起来略有不同,但仍然存在崩溃,它仍然是由将 0 加载到 ecx 并取消引用它造成的.

If I change v1 to be struct tup v1[10]; and set c.p = &v1[0];, then there will be no pointer to an invalid location. But I can still observe the same behaviour. The disassembly looks marginally different, but there is still a crash and it is still caused by loading 0 into ecx and dereferencing it.

结论

所以,这可能是一个错误.我发现如果我改变,崩溃就会消失

So, probably it is a bug. I found out that the crash vanishes if I change

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

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

正如 bames53 告诉我们的那样,崩溃并没有发生在 VS2011 中,并得出结论认为它必须已修复.

As bames53 tells us, the crash does not occur in VS2011 and concludes that it must have been fixed.

尽管如此,我还是出于两个原因决定提交该错误:

Nontheless, I decided to file that bug for two reasons:

  • 这个 bug 可能仍然存在于 VS2011 中.也许优化器只是改变了我的代码不再触发错误的方式.(该错误似乎非常微妙,当我删除 volativevirtual void Reset() 时不会发生)

我想知道我的解决方法是否是排除崩溃的可靠方法,或者其他地方的代码更改是否会重新引入错误.

I want to know if my workaround is a reliable way to rule out the crashes, or if code changes in other places can reintroduce the bug.

这是链接:

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

推荐答案

代码没问题.这是一个编译器错误.

The code is fine. It's a compiler bug.

代码 *(c++) = v2 将后增量 c.p 产生原始值.该值是在上一行中分配的,并且是 &v1.所以,实际上,它确实是 v1 = v2;,这非常好.

The code *(c++) = v2 will post-increment c.p yielding the original value. That value was assigned in the previous line and is &v1. So, in effect, it does v1 = v2;, which is perfectly fine.

cp 现在按照标准的 §5.7p4 表现为单元素数组的过去尾端,该数组仅包含 v1:

c.p now behaves as a one-past-the-end of a one element array that holds only v1, per §5.7p4 of the standard:

对于这些运算符 [+-],一个指向非数组对象的行为与指向第一个元素的指针相同以对象类型为元素的长度为 1 的数组输入.

For the purposes of these operators [+ and -], a pointer to a nonarray object behaves the same as a pointer to the first element of an array of length one with the type of the object as its element type.

然后 *(--c) 将该指针移回 &v1 并取消引用它,这也很好.

Then *(--c) moves that pointer back to &v1 and dereferences it, which is also fine.

相关文章