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

Posted

技术标签:

【中文标题】由于未定义的行为或编译器错误导致 C++ 代码崩溃?【英文标题】:Crash in C++ code due to undefined behaviour or compiler bug? 【发布时间】:2012-05-19 07:53:42 【问题描述】:

我遇到了奇怪的崩溃。我想知道这是否是我的代码或编译器中的错误。 当我使用 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 指针。

我看到两个可能的原因:

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

或存在编译器错误

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

谢谢,

乔纳斯

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

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

编辑:结论

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

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

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

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

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

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

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

这是链接:

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

【问题讨论】:

你让很多人感到困惑。在我看来,它确实像一个编译器错误。应该通过 Microsoft Connect 联系他们。 @将v2 更改为0, 0; 并删除volatile int x 表现出相同的行为,因此不相关。 @ildjarn: bames53 确认它已经在 vs11 中修复。 @Mooing Duck:不,这是必要的。 virtual Reset() 也是必要的。删除这两个中的一个,崩溃就会消失。我认为两者都需要以欺骗优化器,使其无法优化得很好。因为明显的优化是只返回 0。 @m3tikn0b:在我的机器上,当我删除 virtual int x 时问题仍然存在,但我调整了优化设置。所以要么(A)这些设置是相关的,要么(B)我们中的一个人搞砸了我们的测试。 (我承认可能是我) 【参考方案1】:

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

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

c.p 现在按照标准的 §5.7p4 表现为一个仅包含 v1 的单元素数组的末尾:

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

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

【讨论】:

看起来很可能;将 c++/--c 替换为 (c.p)++/--(c.p) 可以消除 vs10 中的崩溃,而 vs11 不会出现崩溃。【参考方案2】:

不一定是 UB 或编译器错误。两者都不是VS2010的制作方式。

严格来说,您的程序表现出明确定义的行为。但是,这可能仅符合最新的 C++ 标准。 VS2010 仅针对可能未包含此规定的标准草案实施。如果没有,那么你的代码不是 UB,但是 VS 生成 UB 并没有错,因为这是它制作时的要求。

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

编辑:如果您仍然遇到您所说的数组崩溃,那么这绝对是一个编译器错误。

【讨论】:

【参考方案3】:

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

【讨论】:

我没想到(&v1)+1 == &v2。我基本上都期待((&v1)+1)-1 == &v1(Luchian 说的可能是错的?) @m3tikn0b:你期望什么并不重要。这不是标准所说的,也不是任何实现必须做的。 @DeadMG 如果单个元素可以被视为一个数组,那么 ((&v1)+1)-1) 应该等于 &v1。尽管我没有引用关于单个元素可以被视为一个数组的说法。 @bames53: C++11 Feb11 Draft § 5.7/4 [expr.add] “对于这些运算符,指向非数组对象的指针与指向第一个元素的指针的行为相同长度为 1 的数组,其元素类型为对象的类型。" @DeadMG : C++98、C++03 和 C++11 中的措辞相同。

以上是关于由于未定义的行为或编译器错误导致 C++ 代码崩溃?的主要内容,如果未能解决你的问题,请参考以下文章

C++ 类型索引散列导致未定义的行为

由于C++类库版本不同导致的OpenCV编译链接错误

C++ IrrKlang 声音错误- CreateIrrKlangDevice() 导致未定义的引用(真正的长引用)

别名可变原始指针 (*mut T) 会导致未定义的行为吗?

TypeScript 编译器崩溃:publicMembers 为 null 或未定义

媒体基础多个视频播放导致内存泄漏和未定义时间范围后崩溃