由于未定义的行为或编译器错误导致 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似乎很微妙,当我删除volative
或virtual 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++ IrrKlang 声音错误- CreateIrrKlangDevice() 导致未定义的引用(真正的长引用)