繁体   English   中英

C ++内存释放

[英]C++ Memory Deallocation

我正在尝试测试我对C ++内存分配的理解。

对于以下程序:

{
    int a=0;
}

由于a是从堆栈中分配的,因此当变量超出范围时应该释放它,对吧?

好的,够容易的。 这个案子怎么样:

{
    Matrix m(50, 20);
}

假设有一个矩阵类,我正在创建一个包含50行和20列的新类。 显然,并非所有内存都可以从堆栈中分配,因为50和20可以在运行时填充。 所以我猜测在构造函数中的某个地方,它们会从堆中分配内存。

当超出范围时,调用m上的析构函数? 那个析构函数应该释放(删除)它分配的内存?

现在真的很难:

{
    Matrix t;
    {
        Matrix m(50, 20);
        t=m;
    }
}

那么会发生什么? 是否被分配到m的内存位置? 或者它是否以m为单位复制数据? 如果t是对m的引用,那么当m超出范围时会发生什么? m上的析构函数是否被调用? 还是等到t超出范围来调用t / m的析构函数?

当超出范围时,调用m上的析构函数? 那个析构函数应该释放(删除)它分配的内存?

是的,一般是的。

现在真的很难:

{
    Matrix t;
    {
        Matrix m(50, 20);
        t=m;
     }
}

那么会发生什么? 是否被分配到m的内存位置? 或者它是否以m为单位复制数据?

会发生什么是调用赋值运算符:

t.operator=(m);

作为Matrix的实现者,您需要确保有效的语义。 有几种可能的方法:

  1. 赋值运算符可以复制m的数据。 在这种情况下,终身和所有权都没有困难。 但是,在这种方法中,分配成本很高。
  2. 赋值运算符可以使t指向与m相同的数据。 这可能是可行的,但需要非常小心以确保正确管理数据的生命周期,并且修改一个矩阵不会意外地修改另一个矩阵。 实现此目的的一种方法是保留指向数据的引用计数指针 ,并在修改数据时使用copy-on-write 一些较早的std::string属于这种类型。

实际上,这很容易。

在第一种情况下,你是对的。 自动分配,自动释放。

在第二种情况下,它并没有什么不同。 Matrix类构造函数处理它需要的任何额外内存,并且它的析构函数应该释放它。

在第三个类中,内部范围变量被复制到外部范围变量。 Matrix类应遵循Rule of Three,因此应正确处理副本。

所有这一切都假设Matrix的正确实现。

C ++内存分配非常简单,对象范围也是如此。 simple. C ++类设计并不简单。

在您的示例中,每次关闭大括号时,本地对象a或m或t(对于上一示例中的外部大括号)超出范围并调用其析构函数。 在int的情况下,析构函数很简单并删除堆栈上的对象。 and for any respectable library, you can assume it deallocates the object correctly. 对于m,它是类的自定义析构函数,对于任何可敬的库,您可以假设它正确地释放了对象。

使t复杂化的不是析构函数,而是来自m的赋值。 would result in the contents of m being copied into t, which would have its own memory on the heap. 在矩阵类的大多数实现中, 将导致的内容被复制到t中,这将在堆上具有其自己的内存。 然后,当每个对象超出范围时,将释放其相应的内存。

如果实现更复杂,那么由类设计人员来确保它正确处理每个对象的破坏(并且你可以正确地使用库)。

What happens then? Does t get assigned to the memory location of m?

它是一个复制构造函数的调用,它默认执行成员明智的复制。

但是如果您重载了赋值运算符,那么您可以分配引用或创建一个完整的新对象。

对于剩下的问题:

对析构函数的调用取决于您是明确调用它还是将其保留为默认值。 默认情况下,当变量或对象超出范围时,将调用它。

你对int是正确的。 调用分配时,会在堆栈上占用一个int大小的空间量,当变量超出范围时,该int将“弹出”堆栈并且内存可以再次使用。

使用Matrix,你不是100%正确的。 仅仅因为数据是在运行时分配的,并不意味着它不能在堆栈上分配,只要堆栈上有空间就可以了。 这里发生的是当你创建矩阵时, 被推到堆栈上。 它在堆栈上占用的空间量取决于它的管理方式。 在它的构造函数中,它可以为堆分配内存,或者它可能只占用大量的堆栈空间。 如果堆栈上没有空间,您将在运行时获得堆栈溢出异常。

你是正确的虽然如果它确实在构造时分配内存,它应该在破坏时清理它 它不必 ,并且在某些情况下它不应该(例如,如果它实际上不再“拥有”该内存,因为它是与另一个对象一起给予/共享它)。

在最后的案例中,会发生什么:

Matrix t;

这将使用类的默认构造函数创建Matrix。 它是否在此保留任何内存取决于构造函数在其中的含义。 它可能会保留内存,但可能不会。

Matrix m(50, 20);

这将使用类的不同构造函数创建Matrix。 同样,它可能会保留内存,但它可能不会(例如它可能不会占用堆栈上的任何空间,直到添加一些“真实”数据)

t=m;

这里发生的事情再次取决于课程。 此时,这不是构造函数,而是t的赋值函数:

Matrix& operator=(const Matrix& m){
    ....
}

被召唤。 一些赋值构造函数将复制数据,一些将复制指向数据的指针而不复制实际数据本身。 但是,在后一种情况下,类应该通过拒绝删除数据来处理m超出范围的条件,而是依赖于t的构造函数来执行它。 但同样,他们没有,并且可能做得很糟糕(特别是控制这变得复杂)

从中获得的关键是,一旦达到类创建和销毁的级别,如何处理内存取决于类的实现。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM