我遇到了奇怪的崩溃。我想知道它是否是我的代码或编译器中的错误。 当我使用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中可能仍然存在该错误。也许优化器只是改变了我的代码不再触发错误的方式。 (该错误似乎非常微妙,当我删除volative
或virtual void Reset()
时)不会发生
我想知道我的解决方法是否是排除崩溃的可靠方法,或者其他地方的代码更改是否可以重新引入错误。
这是链接:
https://connect.microsoft.com/VisualStudio/feedback/details/741628/error-in-code-generation-for-x86
答案 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
)