我首先学习了C#,现在我开始使用C ++。据我所知,C ++中的运算符new
与C#中的运算符不相似。
您可以在此示例代码中解释内存泄漏的原因吗?
class A { ... };
struct B { ... };
A *object1 = new A();
B object2 = *(new B());
答案 0 :(得分:460)
发生了什么
当您编写T t;
时,您正在使用自动存储持续时间创建类型为T
的对象。当它超出范围时,它将自动清理。
当您编写new T()
时,您正在使用动态存储时间创建类型为T
的对象。它不会自动清理。
您需要将指针传递给delete
以便清理它:
然而,你的第二个例子更糟糕:你正在取消引用指针,并制作一个对象的副本。这样你就失去了使用new
创建的对象的指针,所以即使你想要也不能删除它!
你应该做什么
您应该更喜欢自动存储持续时间。需要一个新对象,只需写:
A a; // a new object of type A
B b; // a new object of type B
如果确实需要动态存储持续时间,请将指针存储在自动存储持续时间对象中,该对象会自动删除它。
template <typename T>
class automatic_pointer {
public:
automatic_pointer(T* pointer) : pointer(pointer) {}
// destructor: gets called upon cleanup
// in this case, we want to use delete
~automatic_pointer() { delete pointer; }
// emulate pointers!
// with this we can write *p
T& operator*() const { return *pointer; }
// and with this we can write p->f()
T* operator->() const { return pointer; }
private:
T* pointer;
// for this example, I'll just forbid copies
// a smarter class could deal with this some other way
automatic_pointer(automatic_pointer const&);
automatic_pointer& operator=(automatic_pointer const&);
};
automatic_pointer<A> a(new A()); // acts like a pointer, but deletes automatically
automatic_pointer<B> b(new B()); // acts like a pointer, but deletes automatically
这是一个常见的习惯用法,它由不太具描述性的名称RAII(资源获取是初始化)组成。当您获得需要清理的资源时,将其粘贴在自动存储持续时间的对象中,这样您就不必担心清理它了。这适用于任何资源,无论是内存,打开文件,网络连接,还是您喜欢的任何资源。
这个automatic_pointer
已经以各种形式存在,我刚刚提供了一个例子。标准库中存在一个非常相似的类,名为std::unique_ptr
。
还有一个名为auto_ptr
的旧的(前C ++ 11),但它现在已被弃用,因为它有一种奇怪的复制行为。
然后还有一些更聪明的例子,比如std::shared_ptr
,它允许多个指向同一个对象的指针,并且只有在最后一个指针被销毁时才清理它。
答案 1 :(得分:34)
分步说明:
// creates a new object on the heap:
new B()
// dereferences the object
*(new B())
// calls the copy constructor of B on the object
B object2 = *(new B());
因此,到最后,你在堆上有一个没有指针的对象,因此无法删除。
另一个样本:
A *object1 = new A();
只有忘记delete
分配的内存时,才是内存泄漏:
delete object1;
在C ++中,存在具有自动存储的对象,在堆栈上创建的对象,以及在堆上创建的具有动态存储的对象,您使用new
分配它们并且需要释放自己delete
。 (这大致都是这样)
认为对于使用delete
分配的每个对象,您应该拥有new
。
修改强>
想一想,object2
不一定是内存泄漏。
以下代码只是为了说明一点,这是一个坏主意,不喜欢这样的代码:
class B
{
public:
B() {}; //default constructor
B(const B& other) //copy constructor, this will be called
//on the line B object2 = *(new B())
{
delete &other;
}
}
在这种情况下,由于other
是通过引用传递的,因此它将是new B()
指向的确切对象。因此,通过&other
获取其地址并删除指针将释放内存。
但我不能强调这一点,不要这样做。这只是为了说明问题。
答案 2 :(得分:11)
给出两个“对象”:
obj a;
obj b;
它们不会占用内存中的相同位置。换句话说,&a != &b
将one的值分配给另一个不会改变它们的位置,但会改变它们的内容:
obj a;
obj b = a;
//a == b, but &a != &b
直观地,指针“对象”以相同的方式工作:
obj *a;
obj *b = a;
//a == b, but &a != &b
现在,让我们看一下你的例子:
A *object1 = new A();
这是将new A()
的值分配给object1
。该值是指针,表示object1 == new A()
,但&object1 != &(new A())
。 (注意,此示例不是有效代码,仅用于解释)
由于保留了指针的值,我们可以释放它指向的内存:delete object1;
由于我们的规则,这与delete (new A());
的行为相同,没有泄漏。
对于第二个示例,您正在复制指向对象。值是该对象的内容,而不是实际指针。与其他案例一样,&object2 != &*(new A())
。
B object2 = *(new B());
我们丢失了指向已分配内存的指针,因此我们无法释放它。 delete &object2;
可能看似可行,但由于&object2 != &*(new A())
,它不等同于delete (new A())
,因此无效。
答案 3 :(得分:9)
在C#和Java中,您使用new来创建任何类的实例,然后您不必担心以后会破坏它。
C ++也有一个创建对象的关键字“new”,但与Java或C#不同,它不是创建对象的唯一方法。
C ++有两种创建对象的机制:
使用自动创建,您可以在作用域环境中创建对象: - 在功能或 - 作为类(或结构)的成员。
在一个函数中,您将以这种方式创建它:
int func()
{
A a;
B b( 1, 2 );
}
在课程中,您通常会以这种方式创建它:
class A
{
B b;
public:
A();
};
A::A() :
b( 1, 2 )
{
}
在第一种情况下,退出范围块时会自动销毁对象。这可以是函数中的函数或范围块。
在后一种情况下,对象b与它所属的A实例一起被销毁。
当您需要控制对象的生命周期时,对象将使用new进行分配,然后需要删除才能销毁对象。使用称为RAII的技术,您可以通过将对象置于自动对象中来处理对象的删除,并等待该自动对象的析构函数生效。
一个这样的对象是一个shared_ptr,它将调用“删除”逻辑,但只有当共享该对象的shared_ptr的所有实例都被销毁时才会被调用。
一般情况下,虽然你的代码可能有很多新的调用,但你应该有限制的删除调用,并且应该始终确保从析构函数或放入智能指针的“删除器”对象中调用它们。
你的析构函数也应该永远不会抛出异常。
如果这样做,您将几乎没有内存泄漏。
答案 4 :(得分:9)
B object2 = *(new B());
此行是泄漏的原因。让我们分开一点..
object2是B类型的变量,存储在地址1处(是的,我在这里选择任意数字)。在右侧,你已经要求一个新的B,或一个指向B类对象的指针。程序很乐意给你这个并将你的新B分配到地址2并在地址3中创建一个指针。现在,访问地址2中数据的唯一方法是通过地址3中的指针。接下来,使用*
取消引用指针以获取指针指向的数据(地址2中的数据)。这有效地创建了该数据的副本,并将其分配给在地址1中分配的object2。请记住,它是COPY,而不是原始数据。
现在,问题在于:
你从未将指针存储在可以使用它的任何地方!完成此分配后,指针(用于访问address2的address3中的内存)超出范围且超出您的范围!您不能再在其上调用delete,因此无法清除address2中的内存。你剩下的是address1中address2的数据副本。两个相同的东西坐在记忆中。一个你可以访问,另一个你不能(因为你失去了它的路径)。这就是为什么这是内存泄漏。
我建议你来自你的C#背景,你已经阅读了很多关于C ++指针如何工作的内容。它们是一个高级主题,可能需要一些时间来掌握,但它们的使用对您来说非常宝贵。
答案 5 :(得分:8)
如果它更容易,可以将计算机内存视为酒店,而程序则是在需要时租用房间的客户。
这家酒店的工作方式是您预订房间并在您离开时告诉搬运工。
如果您在不告诉搬运工的情况下将书籍编入房间并离开,搬运工会认为房间仍在使用,并且不会让其他人使用它。在这种情况下,有房间泄漏。
如果您的程序分配内存并且没有删除它(它只是停止使用它),那么计算机认为内存仍在使用中,并且不允许任何其他人使用它。这是内存泄漏。
这不是一个确切的类比,但它可能有所帮助。
答案 6 :(得分:7)
创建object2
时,您正在创建使用new创建的对象的副本,但是您也丢失了(从未分配过)指针(因此以后无法删除它)。为避免这种情况,您必须将object2
作为参考。
答案 7 :(得分:7)
这条线立即泄漏:
B object2 = *(new B());
在这里,您要在堆上创建一个新的B
对象,然后在堆栈上创建一个副本。无法再访问已在堆上分配的那个,从而导致泄漏。
此行不会立即泄漏:
A *object1 = new A();
如果你从来没有delete
d object1
,那么会有泄漏。
答案 8 :(得分:7)
好吧,如果你没有通过将指向该内存的指针传递给new
运算符来释放你使用delete
运算符分配的内存,则会产生内存泄漏。
在上述两种情况中:
A *object1 = new A();
这里你没有使用delete
释放内存,所以如果你的object1
指针超出范围,你就会有内存泄漏,因为你会丢失指针等不能在其上使用delete
运算符。
在这里
B object2 = *(new B());
您正在丢弃new B()
返回的指针,因此永远不能将该指针传递给delete
以释放内存。因此另一个内存泄漏。