我有一段相当简单的测试代码:
#include <stdio.h>
class PG
{
public:
PG(){
m_ptr = new int;
printf("Created PG %i\n", (int)m_ptr);
}
~PG(){
printf("Deleted PG %i\n", (int)m_ptr);
delete (m_ptr);
}
PG& operator =(const PG& src)
{
printf("Copied PG %i %i\n", (int)m_ptr, (int)src.m_ptr);
return(*this);
}
private:
int * m_ptr;
};
PG CreatePG()
{
PG ret;
return ret;
}
int main(int argc, char* argv[])
{
PG test;
test = CreatePG();
printf("Ending\n");
return 0;
}
如果我使用GCC,VS2008或VS2012进行全面优化编译并运行它,我得到了我期望的结果:
创建了PG 7837600 创建的测试
创建PG 7689464 - 创建ret
复制PG 7837600 768946 -copied ret to test
已删除PG 7689464 -deleted ret
结束
删除了PG 7837600 - 删除测试
然而,当我在没有优化的VS2008或VS2012上编译时,我得到了这个:
创建了PG 3888456 创建的测试
创建PG 4036144 - 创建ret
已删除PG 4036144 -deleted ret。等等,我们还没有复制它!
复制PG 3888456 4036144 - 我们现在正在尝试复制已删除的数据
已删除PG 4036144 - 此内容已被删除。应用程序崩溃
我无法相信它是VS中的一个从未修复过的错误,但我也看不出我做错了什么。在我的应用程序中,我有一个实例,当编译一个针对速度优化的更复杂的类时,也会出现这种情况。我知道使用它会更有效:
PG test = CreatePG();
但我仍然遇到类似的问题,尽管在这种情况下显然使用了复制省略:
创建PG 11228488
已删除PG 11228488
结束
已删除PG 11228488
我仍然得到双重删除。
如果有人能对此有所了解,我将非常感激。
答案 0 :(得分:4)
这是因为您的代码违反了rule of three:因为您没有复制构造函数,所以在您看不到打印输出的场景后面会发生一些重要的事情。
当您没有复制构造函数时,C ++很乐意为您定义一个。这通常是您想要的确切构造函数,但在一种情况下:当您的类显式管理资源时。在这种情况下,当可以多次删除同一指针时,逐字节复制内容会产生错误别名。打开优化时,编译器会跳过复制构造函数的调用(返回值优化)。但是,关闭优化后,将调用复制构造函数,然后删除m_ptr
的副本,使实际指针指向已删除的内存。
以下是解决此问题的方法:
PG& operator =(const PG& src) {
*m_ptr = *(other->m_ptr);
printf("Assigned PG %x %x\n", (void*)m_ptr, (void*)src.m_ptr);
return(*this);
}
PG(const PG& other) {
m_ptr = new int;
*m_ptr = *(other->m_ptr);
printf("Copied PG %x\n", (void*)m_ptr);
}
注意:未定义将指针转换为int
;您应该将指针转换为void*
,并使用%x
格式说明符进行打印。
答案 1 :(得分:0)
你的拷贝构造函数在哪里?你需要一个复制构造函数 为了返回一个值,你所拥有的只是默认副本 构造函数,它将导致多次删除 对象,如果它被使用(因为你最终会有多个 包含相同指针的对象)。至于它为什么做一个 优化后的东西,而不是的时候,大概是NRVO (命名返回值优化)仅在发生时发生 代码已经过优化。
答案 2 :(得分:0)
这就是你遇到问题的原因。
让我们逐步完成您的代码
PG test;
调用PG :: PG()创建一个带有ptr的新PG,在地址X的堆上分配一个新的int,打印“Created PG X”
test = CreatePG();
首先调用CreatePG()
PG ret;
调用PG :: PG()创建一个带有ptr的新PG,在地址Y的堆上分配一个新的int,打印“Created PG Y”
return ret;
由于函数声明为按值返回,因此使用返回ret的副本 执行按位复制的默认复制构造函数。所以未命名副本的m_ptr 返回的是Y.
当我们离开CreatePG()的范围时,将调用本地对象的析构函数,并进行打印 “删除PG Y”然后删除Y.然后我们回到main()中的赋值。
test = CreatePG();
我们现在正在使用operator =分配临时未命名的PG进行测试。这将打印“Copied PG X Y”,然后只返回测试参考而不实际执行任何操作。
在表达式结束时,临时超出范围并调用析构函数。析构函数打印“Deleted PG Y”并尝试删除Y,但Y已被删除,因此这是一个非常大的问题。
编写处理堆指针的复制构造函数的建议很好,可以帮助您解决问题。