Before Syria

In the previous three articles, From the quick start to the details to the features of C++ argument passing, this time we started with the most basic, which happens to be the most important part, memory management, Why is it important? Because C++ does not provide a complete garbage collection mechanism like Java, even if it does, it is relatively simple and cannot be regarded as a perfect rely on, but just because developers can control their own memory, to achieve more efficient memory management, although nowadays it seems that memory is not so important. That’s why languages like Java and Python have become so popular. They literally trade space for time. But as memory managers who strive for perfection, we strive not only for shorter time, but also for smaller space, which we can’t do without C or C++. In fact, Java, as the successor language of C++, actually borrowed from C++, but C++ has RALL, which is the unique resource management mode of C++. Let’s first learn these three concepts.

The stack

In memory management, it is a memory area for variables generated during function calls, function parameter values, return variables, and so on. It is similar to the stack data structure and follows the last in, first out characteristics. In Java, stacks (virtual machine stacks, local method stacks) are thread-private memory. The memory management of the stack is easy to understand, that is, when the stack is removed, variables and objects are freed. How is it freed? Let’s look at an exampleIf it is an object, it has a constructor, etc. The destructor will be called when it is released. Even if an exception exits, C++ memory management will execute the destructor of the object to free it. So do you see what destructors do?

The heap

In C++, as in Java, the region is allocated dynamically, and the new keyword is used to claim space, but the only difference is that in C++, the delete that needs to be displayed can be released, whereas in Java, it is reclaimed by GC. So in C++, if you create an object with new, you have to team up with delete, but there’s another problem with C++, you don’t usually just delete after you’re new, the actual scenario is that you’re new, you have to do a lot of things, and then delete, but you can crash in between, As a result, the program fails to perform delete operation according to the previous idea, which results in a memory leak. The memory can never be released, and after a long time, the application memory will be full, and it cannot apply for new space. In fact, C++ provides us with smart Pointers to gracefully release the memory. Later we will find a topic to study how to better reclaim memory.As you can see in the figure, when we do new, actually memory management, it goes through allocating memory, and actually allocating and freeing memory, it also considers the following scenario:

  1. There is plenty of memory, so take a block of memory of the right size from the available memory
  2. I have enough memory but I don’t have the right amount of available memory, so in this case the other thing that memory management does is it merges unused memory, why is that? Just look at the picture and you’ll see

In this case, memory management is required for defragmentation. Fortunately, since C++ has a dedicated memory fragmentation management mechanism, we do not need to manage the second case. We only care about the correct new and delete.

  1. If the memory is insufficient, apply for new memory from the operating system

RALL

Resource Acquisition Is Initialization. It comes from C++ and Is also used in Java. I don’t know how to use it, so you can study it if you are interested. Source: biani stroustrup and Andrew koenig used it to address exception security in resource management when designing C++ exceptions. RAII requires that the lifetime of a resource is strictly bound to the lifetime of the object that holds it, with the object’s constructor doing the allocating (fetching) and destructor doing the releasing. Under this requirement, as long as the object is properly destructed, there is no resource leak problem. In fact, RAII relies on stacks and destructors to manage all resources, including heap memory, so it manages stacks, heaps, and other resources. In C++, objects can be created on top of a stack, but the stack memory is usually small, and it is a contiguity area of memory. Unlike a heap, which can use discrete memory areas, the bottom layer is made up of linked lists. Under Windows, the stack size is 2MB, while under Linux, the default stack size is 8MB, which can be modified. So, if you put objects all on the stack and don’t use heap memory, that’s definitely not enough. So whether it’s a parameter, a variable declared in a function, a return value, if it’s an object, we mostly rely on references or Pointers, and the values of the references and the values of the Pointers are actually in the heap. The same goes for Java. To better understand RALL, let’s look at an example


class TestRALL {
public:
    TestRALL() {
        std::cout << "TestRALL done" << std::endl;
    };

    ~TestRALL() {
        std::cout << "~TestRALL done" << std::endl;
    };

    void print(a) {
        std::cout << 1<< std::endl; }};TestRALL *createTest(a) {
    return new TestRALL(a); }void print(a) {
    auto ta = createTest(a); ta->print(a); }int main(a) {
    print(a);return 0;
}
Copy the code

After executing main, the following output is displayed:

TestRALL done
1
Copy the code

It is found that the destructor is not called, meaning that the TestRALL object is always there. If I add a row like this

void print(a) {
    auto ta = createTest(a); ta->print(a);delete ta;
}
Copy the code

print

TestRALL done
1
~TestRALL done
Copy the code

In fact, I’m explicitly calling delete, which is usually not scientific, so what should I do? Let’s look at the following example


class TestRALL {
public:
    TestRALL() {
        std::cout << "TestRALL done" << std::endl;
    };

    ~TestRALL() {
        std::cout << "~TestRALL done" << std::endl;
    };

    void print(a) {
        std::cout << 1<< std::endl; }};TestRALL *createTest(a) {
    return new TestRALL(a); }class TRDelete {
public:
    explicit TRDelete(TestRALL *tr = nullptr) : tr_(tr) {}

    ~TRDelete() {
        delete tr_;
    }

    TestRALL *get(a) const { return tr_; }

private:
    TestRALL *tr_;
};

void print(a) {
    TRDelete trDelete(createTest());
    trDelete.get() - >print(a); }int main(a) {
    print(a);return 0;
}
Copy the code

First, a new explanation:

explicit

After constructors are explicit, they cannot be called implicitly. What is implicit calling? Here’s an example:

#include <iostream>
using namespace std;

class Point {
public:
    int x, y;
    Point(int x = 0.int y = 0)
        : x(x), y(y) {}
};

void displayPoint(const Point& p) 
{
    cout << "(" << p.x << "," 
         << p.y << ")" << endl;
}

int main(a)
{
    displayPoint(1);
    Point p = 1;
}
Copy the code

DisplayPoint is implicit invocation, which seems to simplify code, but why not? From Effective C++, because: constructors declared as explicit are often more popular than their non-explicit siblings because they prohibit the compiler from performing unexpected (and often not expected) type conversions. I declare the constructor explicit unless I have a good reason to allow it to be used for implicit conversions. I encourage you to follow the same policy. If we go back to TRDelete, in its destructor, we delete TestRALL, after print, we didn’t execute delete TRDelete so why did it execute TRDelete destructor? TRDelete is not created by new. It calls TRDelete’s destructor when the function exits the stack. This is a basic use of RALL. There are smarter ways to use it, and we’ll talk about that later.

Simple summary

This time we did the stack, heap, RALL memory management features to learn and practice, also know that can be through RALL on the stack and heap unified memory management of a small use, of course, we learn to follow a gradual, step by step a JUO printing, welcome you to comment on ha.