C++ memory management (1)

The day before yesterday, I was very interested in the underlying memory mechanism, and happened to brush the C++ memory management mechanism of Teacher Hou Jie at station B. It took three days one after another and finally finished. As the title of the video says-from flat ground to ten thousand-foot tall buildings, I really learned a lot. I wrote the following viewing notes to share some of my understanding and experience.

First talk about Primitives

Insert picture description here

This picture illustrates the four ways for a C++ application to apply for memory, each of which can realize the application and call of memory and has a hierarchical relationship. For example, when we use the C++ standard library STL container to create an object, the container makes a memory application to the allocator, and then the allocator applies for memory to the next level, and so on. This process is encapsulated. In our eyes, we just create a container object, and the system allocates a piece of memory to us for use, but we don't know the principle. This is also the meaning of the existence of such a lesson, telling us how these memory is allocated efficiently. Returning to the topic, when allocator applies for memory to the next level, it will actually call new, new[], ::opeartor new(), etc. We all know that these primitives are keywords for applying memory in the C++ standard grammar. . So the task of applying for memory is iterated to primitives such as new, new[], ::opeartor new(), and then analyzes the source code, we will find that in new, malloc is actually called to allocate memory , And further down is the memory allocation function provided by the operating system. In fact, we don't need to go down from level to level. We can apply for memory from any step in the middle, so why don't we directly call the memory application function provided by the operating system at the lowest level in the code? In fact, the answer is obvious. We encapsulate the underlying functions layer by layer, and the benefits of encapsulation allow programmers to use them efficiently and conveniently to achieve our goals.

Insert picture description here
This picture shows the actual operation of calling the new function to apply for memory. He will first call the operator new function. This function can be overloaded, that is, to overload the ::operator new function (global), by ourselves Manually write the memory allocation function. In operator new(), we can clearly see that the malloc function is actually called to allocate memory. Another key step is while calling if(_callnewh(size)==0). This step provides a strategy just in case The reason malloc fails, it will not throw bad_alloc exception first, but call a _callnewh(size) (rewriteable call new hander) function to try to release unnecessary memory to successfully apply for memory. After the memory is allocated, the constructor of the object will be performed to complete the memory application. This is the first layer principle inside new.
Insert picture description here
Delete internally is similar. As we can see from the source code, the object is first destructed, and then operator delete is called, essentially using free to release the memory.
Insert picture description here
We all know that if you want to dynamically allocate a group of objects, you need to add []. At this time, you will call up multiple constructors to construct the corresponding objects. When you release the memory, you also need to add []. At this time, the this pointer called by dtor points to Different objs are deconstructed separately to release the memory. What effect will it have if you don’t add [] when releasing memory? The bottom left corner of the figure tells us that the data structure of the object array we applied for has a cookie. The cookie records the size of the dynamically applied memory, so when releasing, we only need to know the psa base address plus the size of the cookie record to directly release the application. , Then this shows that we do not need to add [], so that dtor only calls the destructor of the object on the top of the stack once. But if the object we apply for contains pointer members, we cannot release the pointer space of all member objects by calling the destructor only once, and this will cause memory leaks. So this is why delete[] has no effect on the release of int char objects in the standard library, but there will be memory leaks in the release of custom objects. This is the reason why the pointer is wrong. Note that the order is reversed when the object is destroyed.
Insert picture description here
Placement new is very interesting. Placement new is an overloaded version of operator new, but we rarely use it. If you want to create an object in the allocated memory, using new will not work. In other words, placement new allows you to construct a new object in an already allocated memory (stack or heap). The second sentence in the above figure is to use placement new to create a Complex pointer object in buf memory. We can see from the source code that it does not actually call malloc to allocate memory but directly returns buf. At this time, the Complex object pointer pc points to buf. First address and store the object, so as to achieve the purpose of design.
Insert picture description here
This picture clearly illustrates the method of memory management. First look at the picture. As mentioned above, when calling new, we will enter the operator new function. If we do not rewrite the global ::operator new function at this time, then we will perform malloc operations after entering the ::operator new function, and if we When the operator new function is rewritten inside the class, the priority of the function we rewrite will be higher, and it will run first. This shows that we can rewrite the operator new function inside the class to achieve memory management . For example, in the next lecture, we will write a _pool_memory data structure to manage memory and achieve efficient memory allocation.
How can we achieve efficient memory management?

1. In the new array, we repeatedly call malloc to apply for memory allocation to allocate small memory. When the amount of data is large enough, such as millions of times, it is always bad to call malloc repeatedly. In other words, it is always good to reduce the number of malloc calls. of. So can we first malloc a large block of memory as a reserve memory pool, and cut out small memory directly from the memory pool for use when it needs to be allocated later. As shown in the upper left corner. (Speed)
2. For the memory we applied for, suppose we applied for 10 bytes of memory and succeeded, but in fact the operating system gave us more than 10 bytes (the next lecture will have a memory structure diagram), this is because In each malloc, the memory we applied for contains two cookies (according to the compiler version), and one cookie occupies 4 bytes (32-bit system), then if malloc is 1 million times, then there will be 8 million words Section’s cookie waste. So can we reduce the use of cookies without affecting the function of cookies through a specific and ingenious data structure, and achieve efficient memory allocation? (Spatially)

Insert picture description here
The same is true in the container, an additional layer of allocator goes down to apply for memory, but the principle remains the same as the above figure.

Well, now we know that the memory management method is the above two ideas, the method is to rewrite the operator new function to take over the implementation of memory allocation. Start the actual combat without saying a word .

1.1 Memory management
Insert picture description here
in C++Primer In the Screen class, we first look at the main data members. There is an int type variable, a next pointer to itself, and two static data members. Among them, freeStroe points to the head of a large block of memory obtained by the application. Pointer, screenChunk is a static constant 24. At this time sizeof(Screen)=sizeof(obj), that is, the size of an object is greater than or equal to the sum of the sizes of all non-static members, so it is 8 bytes. Then look at the member functions of the class, the rewritten operator new and delete implement memory management. In the operator new function, we define a chunk as the number of large memory bytes we want to get, and then use the new application to obtain the required large memory and assign it to the freeStore pointer, and then use the for loop to slice the large memory to obtain the small memory for each Malloc allocation. In operator delete, we use the head insertion method of the linked list to insert the reclaimed object space back into the linked list and move the freeStore pointer.
Insert picture description here
From the test instance, we can see that the Screen class after rewriting operator new has two cookies missing when creating objects, and each object has 8 bytes less, which is in line with the memory allocation on space. This model also has shortcomings. When designing, we designed an extra pointer and only thought about ourselves, so that the original seziof size was doubled. In this case, although it is an int data member, there may be multiple variables in practice. 100% of expansion rate. As a result, although we reduced the cookie, but increased the size of the data itself, so we extended the next version.

1.2 Memory management in C++Primer is
Insert picture description here
still the same. First look at the data members. A structure and a union are defined, and two static variables are defined. I have a lot of questions about the size of the class structure. If, like the design on the way, the size of the union of the undefined structure variable and the undefined union variable is 0, the output of seziof should be 1. This is a doubt. . Suppose we define variables for both the structure variable and the union variable. According to the memory alignment principle, the first structure occupies 8 bytes. The starting offset of all variables in the union is the same, so the size is the union The element with the largest size in the body also occupies 8 bytes, so the total sizeof should output 16. This is the second doubt. However, the size in Hou Jie's video and subsequent test cases is indeed 8 bytes, which is really confusing. Readers are welcome to comment and explain . For convenience, we assume that it is 8 bytes. There are also two static member variables in the class, similar to version 1.1, one is a constant to obtain a large block of memory, and a pointer is used to point to the developed memory linked list. Looking at the member functions, the main thing is to rewrite operator new and delete. In operator new, the working principle is similar to that of version 1.1, but the global ::operator new is used to apply for memory, and operator delete is also similar, so I won't explain it too much.

Comparing the 1.1 and 1.2 versions, we find that the biggest difference is the use of union. The first four bytes of union are used to set the next pointer. Because the memory is shared in the union, we use the concept of embedded pointers to use the first four when the memory is not allocated. The bytes are converted into next pointers. After allocation, write data to this block of memory to overwrite the contents of the pointer to make the pointer invalid. In this way, optimization can be achieved, which not only reduces the number of cookies to two blocks at the beginning and the end, but also saves pointers. Own pointer to achieve great optimization in space.

Insert picture description here
Checking the test sample is indeed as expected.

Because it is very troublesome to rewrite operator new and delete in this way in each function, the code is redundant, so we can design a special class to do memory allocation, so the allocator appears.
Insert picture description here
The code has been slightly changed, but the principle is similar, so I won't talk about it again. At this time, to allocate memory in the class, you only need to define a static allocator to allocate memory for each object.

Finally, there is a function that I think is very useful, which is worth taking notes, that is, =delete and =default in C++11. Add the =default specifier to the end of the function declaration to declare the function as the display default constructor. Using the =delete specifier to disable any member function it uses is also valid for our rewritten operator new and delete, and will come in handy in some special occasions.

At this point, the first lecture is over. From the flat ground to the ten thousand-foot tall building, it can be regarded as the first floor.

Guess you like

Origin blog.csdn.net/GGGGG1233/article/details/114989004