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

时间:2012-05-10 17:20:18

标签: c++ visual-studio-2010 visual-c++ crash compiler-optimization

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

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

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

在偏移00CE1017时,它取消引用0指针。

我认为有两个可能的原因:

  • 我的代码中有一些未定义行为的微妙(或不那么微妙?)的情况 并且编译器将此未定义的行为“优化”为崩溃。

  • 或存在编译器错误

有人看到可能导致问题的原因吗?

谢谢,

纳斯

编辑:解决有关“指向无效位置的指针”的评论

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

编辑:结论

所以,可能是一个bug。我发现如果我改变

,崩溃就会消失
struct tup* operator--() { return --p; }

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

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

尽管如此,我还是决定提交这个bug有两个原因:

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

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

这是链接:

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

3 个答案:

答案 0 :(得分:17)

代码很好。这是一个编译器错误。

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

c.p现在表现为一个元素数组的结尾,只有v1,符合标准的§5.7p4:

  

出于这些运算符[+-]的目的,指向a的指针   nonarray对象的行为与指向第一个元素的指针相同   一个长度为1的数组,其中对象的类型为其元素   类型。

然后*(--c)将指针移回&v1并取消引用它,这也没关系。

答案 1 :(得分:1)

它不一定是UB或编译器错误。这可能不是由于VS2010的生产方式。

严格地说,您的程序展示了明确定义的行为。但是,这可能只是根据最新的C ++标准。 VS2010仅针对可能未包含此规定的标准草案实施。如果没有,那么你的代码不是UB,但VS生成UB并不正确,因为那些是它所用的时间要求。

当然,如果在C ++ 03中将堆栈对象视为一个对象的数组是合法的,那么它就是编译器错误。

编辑:如果您在说明时仍然遇到阵列崩溃,那那肯定是编译器错误。

答案 2 :(得分:-3)

您将&v1带入c.p,稍后使用operator ++推进它。您不能依赖堆栈的顺序,因此会出现未定义的行为((&v1)+1 != &v2