简体   繁体   English

C ++内存释放

[英]C++ Memory Deallocation

I'm trying to test my understanding of C++ memory allocation. 我正在尝试测试我对C ++内存分配的理解。

For the following program: 对于以下程序:

{
    int a=0;
}

Since a is allocated off the stack it should be freed when the variable goes out of scope, right? 由于a是从堆栈中分配的,因此当变量超出范围时应该释放它,对吧?

Okay, easy enough. 好的,够容易的。 What about this case: 这个案子怎么样:

{
    Matrix m(50, 20);
}

Let's say there's a matrix class and I'm creating a new one with 50 rows and 20 columns. 假设有一个矩阵类,我正在创建一个包含50行和20列的新类。 Obviously not all of the memory can be allocated off the stack because 50 and 20 could be populated at run time. 显然,并非所有内存都可以从堆栈中分配,因为50和20可以在运行时填充。 So I'm guessing that somewhere in the constructor, they allocate memory off the heap. 所以我猜测在构造函数中的某个地方,它们会从堆中分配内存。

When that goes out of scope the destructor on m is called? 当超出范围时,调用m上的析构函数? And that desctructor should deallocate (delete) the memory it allocated? 那个析构函数应该释放(删除)它分配的内存?

Now it really gets hard: 现在真的很难:

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

What happens then? 那么会发生什么? Does t get assigned to the memory location of m? 是否被分配到m的内存位置? or does it do a copy of the data in m? 或者它是否以m为单位复制数据? If t is a reference to m, then what happens when m goes out of scope? 如果t是对m的引用,那么当m超出范围时会发生什么? Does the destructor on m get called? m上的析构函数是否被调用? or does it wait until t goes out of scope to call the destructor of t/m? 还是等到t超出范围来调用t / m的析构函数?

When that goes out of scope the destructor on m is called? 当超出范围时,调用m上的析构函数? And that desctructor should deallocate (delete) the memory it allocated? 那个析构函数应该释放(删除)它分配的内存?

Yes and generally yes. 是的,一般是的。

Now it really gets hard: 现在真的很难:

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

What happens then? 那么会发生什么? Does t get assigned to the memory location of m? 是否被分配到m的内存位置? or does it do a copy of the data in m? 或者它是否以m为单位复制数据?

What happens is that the assignment operator is called: 会发生什么是调用赋值运算符:

t.operator=(m);

It is up to you, the implementer of Matrix , to ensure valid semantics. 作为Matrix的实现者,您需要确保有效的语义。 There are several possible approaches: 有几种可能的方法:

  1. The assignment operator could make a copy of m 's data. 赋值运算符可以复制m的数据。 In this case there are no difficulties with lifetime and ownership. 在这种情况下,终身和所有权都没有困难。 However, in this approach the assignment is costly. 但是,在这种方法中,分配成本很高。
  2. The assignment operator could make t point to the same data as m . 赋值运算符可以使t指向与m相同的数据。 This may be viable, but will require a lot of care to make sure the lifetime of the data is managed correctly, and that modifying one matrix doesn't unexpectedly modify the other. 这可能是可行的,但需要非常小心以确保正确管理数据的生命周期,并且修改一个矩阵不会意外地修改另一个矩阵。 One way to do this is by keeping a reference-counted pointer to the data, and using copy-on-write when modifying data. 实现此目的的一种方法是保留指向数据的引用计数指针 ,并在修改数据时使用copy-on-write Some older implementations of std::string are of this type. 一些较早的std::string属于这种类型。

Actually, it's pretty easy. 实际上,这很容易。

In the first case, you're right. 在第一种情况下,你是对的。 Automatic allocation, automatic deallocation. 自动分配,自动释放。

In the second case, it's not really any different. 在第二种情况下,它并没有什么不同。 The Matrix class constructor handles any additional memory it needs, and its destructor should deallocate it. Matrix类构造函数处理它需要的任何额外内存,并且它的析构函数应该释放它。

In the third class, the inner scope variable is copied to the outer scope variable. 在第三个类中,内部范围变量被复制到外部范围变量。 The Matrix class should be following the Rule of Three, so copies should be handled correctly. Matrix类应遵循Rule of Three,因此应正确处理副本。

All of this is assuming a proper implementation of Matrix. 所有这一切都假设Matrix的正确实现。

C++ memory allocation is really simple, so is object scope. C ++内存分配非常简单,对象范围也是如此。 C++ class design is not as simple. C ++类设计并不那么简单。

In your examples, each time you close a brace, the local objects, a or m or t (for the outer set of braces in your last example), go out of scope and their destructor is called. 在您的示例中,每次关闭大括号时,本地对象a或m或t(对于上一示例中的外部大括号)超出范围并调用其析构函数。 In the case of the int, the destructor is simple and deletes the object on the stack. 在int的情况下,析构函数很简单并删除堆栈上的对象。 For m, it's a custom destructor for the class Matrix and for any respectable library, you can assume it deallocates the object correctly. 对于m,它是Matrix类的自定义析构函数,对于任何可敬的库,您可以假设它正确地释放了对象。

What complicates t is not the destructor, but the assignment from m. 使t复杂化的不是析构函数,而是来自m的赋值。 In most implementations of matrix classes, t = m would result in the contents of m being copied into t, which would have its own memory on the heap. 在矩阵类的大多数实现中, t = m将导致m的内容被复制到t中,这将在堆上具有其自己的内存。 Then as each object goes out of scope, their corresponding memory is deallocated. 然后,当每个对象超出范围时,将释放其相应的内存。

If the implementation is more complex, then its up to the class designer to make sure it handles the destruction of each object correctly (and you to use the library correctly so that it can). 如果实现更复杂,那么由类设计人员来确保它正确处理每个对象的破坏(并且你可以正确地使用库)。

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

It's a call to copy constructor which do member wise copy by default. 它是一个复制构造函数的调用,它默认执行成员明智的复制。

but if you have overloaded an assignment operator then you can either assign the reference or create a complete new object its up to you. 但是如果您重载了赋值运算符,那么您可以分配引用或创建一个完整的新对象。

for rest of the Ques: 对于剩下的问题:

call to destructor depends on you either you call it explicitly or leave it to default. 对析构函数的调用取决于您是明确调用它还是将其保留为默认值。 in default it will be called when the variable or object is out of scope. 默认情况下,当变量或对象超出范围时,将调用它。

You're correct with the int. 你对int是正确的。 When the allocation is called, an int size amount of space is taken up on the stack, and when the variable goes out of scope, that int is "popped off" the stack and the memory is free to be used again. 调用分配时,会在堆栈上占用一个int大小的空间量,当变量超出范围时,该int将“弹出”堆栈并且内存可以再次使用。

With the Matrix, you're not 100% right. 使用Matrix,你不是100%正确的。 Just because the data is allocated at run-time, doesn't mean it can't be allocated on the stack, as long as there's room on the stack to put it. 仅仅因为数据是在运行时分配的,并不意味着它不能在堆栈上分配,只要堆栈上有空间就可以了。 What happens here is that when you create the matrix, it is pushed onto the stack. 这里发生的是当你创建矩阵时, 被推到堆栈上。 The amount of space it takes up on the stack is dependent on how it's managed. 它在堆栈上占用的空间量取决于它的管理方式。 Within its constructor, it may allocate memory to the heap, or it may simply take up a big ol' chunk of stack space. 在它的构造函数中,它可以为堆分配内存,或者它可能只占用大量的堆栈空间。 If there isn't room on the stack, you'll get a stack overflow exception at runtime. 如果堆栈上没有空间,您将在运行时获得堆栈溢出异常。

You are correct though that if it does allocate memory at construction, it should clean it up at destruction . 你是正确的虽然如果它确实在构造时分配内存,它应该在破坏时清理它 But it doesn't have to , and in some cases it shouldn't (eg if it doesn't actually "own" that memory anymore because it's given/sharing it with another object). 它不必 ,并且在某些情况下它不应该(例如,如果它实际上不再“拥有”该内存,因为它是与另一个对象一起给予/共享它)。

In your final case, what happens is this: 在最后的案例中,会发生什么:

Matrix t;

This creates a Matrix using the default constructor of the class. 这将使用类的默认构造函数创建Matrix。 Whether it reserves any memory here is dependent on what that constructor has in it. 它是否在此保留任何内存取决于构造函数在其中的含义。 It might reserve memory, but it might not. 它可能会保留内存,但可能不会。

Matrix m(50, 20);

This creates a Matrix using a different constructor of the class. 这将使用类的不同构造函数创建Matrix。 Again, it might reserve memory, but it might not (eg it might not take up any space other than what's on the stack till some "real" data is added to it) 同样,它可能会保留内存,但它可能不会(例如它可能不会占用堆栈上的任何空间,直到添加一些“真实”数据)

t=m;

What happens here is, again, up to the class. 这里发生的事情再次取决于课程。 At this point, this is not a constructor, but the assignment function of t: 此时,这不是构造函数,而是t的赋值函数:

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

being called. 被召唤。 Some assignment constructors will copy the data, some will copy pointers to the data but not the actual data itself. 一些赋值构造函数将复制数据,一些将复制指向数据的指针而不复制实际数据本身。 However, in the latter case, the class should handle the condition where m goes out of scope, by refusing to delete the data, and instead relying on the constructor of t to do it. 但是,在后一种情况下,类应该通过拒绝删除数据来处理m超出范围的条件,而是依赖于t的构造函数来执行它。 But again, they don't have to, and may do it badly (especially as controlling this gets complicated) 但同样,他们没有,并且可能做得很糟糕(特别是控制这变得复杂)

The critical take from this is that once you reach the level of class creation & destruction, how memory is handled is dependent on the implementation of the class. 从中获得的关键是,一旦达到类创建和销毁的级别,如何处理内存取决于类的实现。

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

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