Note: memory management is always the red-light area of the C/C ++ program. There are two focuses on memory management. One is the correct use of memory. For example, new and delete in C ++ should appear in pairs and use raiI to manage memory resources, auto_ptr and other aspects, many C/C ++ books use the introduction of skills. The other is the implementation of memory management, such as the slab distributor of the Linux kernel, Allocator implementation in STL, and memory management specific to some objects. I recently read some information and source code on memory management implementation, sorted it out, and compiled it into a series to introduce some common memory management policies.
1. Introduction to STL containers
STL provides many generic containers, such as vector, list, and map. When using these containers, programmers only need to worry about when to plug objects into the container, instead of how to manage the memory and how much memory is needed. These STL containers greatly facilitate the compilation of C ++ programs. For example, you can use the following statement to create a vector, which is actually an on-demand dynamic array. The type of each element is int INTEGER:
STL: vector <int> array;
With such a dynamic array, you only need to call the push_back method to add objects to it, without considering the amount of memory required:
Array. push_back (10 );
Array. push_back (2 );
The vector will automatically increase the memory as needed, and the occupied memory will be automatically destroyed when the array exits its scope, which is transparent to users, STL containers cleverly avoid tedious and error-prone memory management.
2. Default memory distributor of STL
Memory Management after these containers is implemented through a default Allocator provided by STL. Of course, you can also customize your Own Allocator by implementing the interface method defined in the Allocator template, and then passing the custom Allocator as a template parameter to the STL container, create an STL container object that uses custom Allocator, for example:
STL: vector <int, userdefinedallocator> array;
In most cases, the default STL allocator is enough. This allocator is a memory manager composed of two-level distributors. When the applied memory size is greater than bytes, the first-level distributor is started to directly allocate the heap space to the system through malloc, if the requested memory size is less than bytes, the second-level distributor is started to deliver a piece of memory from a pre-allocated memory pool to the user, the memory pool consists of 16 different sizes (multiples of 8, 8 ~ 128 byte). Allocator extracts the header block from the corresponding idle block list based on the Applied memory size (which is a multiple of 8.
This method has two advantages:
1) Fast allocation of small objects. Small objects are allocated from the memory pool. This memory pool is used by the system to allocate a large enough area to the program for backup once malloc is called, when the memory pool is exhausted, apply for a new area from the system. The whole process is similar to wholesale and retail. Allocator first wholesalers a certain amount of goods to the general business, and then retail it to the user, it is faster than the process in which a commodity is always sold to the user. Of course, when there is a problem here, the memory pool will cause some memory waste. For example, if you only need to allocate a small object, you may need to apply for a large memory pool for this small object, however, this waste is worthwhile. Moreover, this situation is rare in practical applications.
2) avoid the generation of memory fragments. The allocation of small objects in the program can easily cause memory fragmentation, which puts a lot of pressure on the memory management of the operating system. The increase of fragments in the system will not only affect the speed of memory allocation, it also greatly reduces the memory usage. The memory pool organizes the memory of small objects. From the system perspective, it is only a large memory pool, and the allocation and release of small object memory cannot be seen.
During implementation, Allocator needs to maintain an array free_list that stores 16 idle Block List headers. The array element I is an array with a size of 8 * (I + 1) pointing to the block) header of the byte free Block List, a pointer to the start address of the memory pool start_free and a pointer to the end address end_free. The structure of the idle Block List node is as follows:
Union OBJ {
Union OBJ * free_list_link;
Char client_data [1];
};
This structure can be seen as extracting 4 bytes from a memory block. When the memory block is idle, it stores the next idle block, when this memory block is delivered to the user, it stores user data. Therefore, the idle block linked list in Allocator can be expressed:
OBJ * free_list [16];
3. Allocation Algorithm
The memory allocation algorithm for allocator is as follows:
Algorithm: Allocate
Input: size of the applied memory
Output: if the allocation is successful, a memory address is returned; otherwise, null is returned.
{
If (size greater than 128) {start the first-level distributor to directly call malloc to allocate the required memory and return the memory address ;}
Else {
The size is rounded up to a multiple of 8 and the corresponding header free_list_head is retrieved from free_list based on the size;
If (free_list_head is not empty ){
Remove the first idle block from the list and adjust free_list;
Returns free_list_head;
} Else {
Call the refill algorithm to create a list of idle blocks and return the desired memory address;
}
}
}
Algorithm: refill
Input: memory block size
Output: Create an idle block linked list and return the first available memory block address.
{
Call the chunk_alloc algorithm to allocate several consecutive memory regions with size and return the chunk of the starting address and the number of successfully allocated blocks nobj;
If (number of blocks is 1), Chunk is returned directly;
Otherwise
{
Create free_list In the chunk address block;
Obtain the corresponding Header element free_list_head in free_list based on the size;
Point free_list_head to the address with the starting offset address of size in chunk, that is, free_list_head = (OBJ *) (chunk + size );
Then, the remaining nobj-1 memory blocks in the entire chunk are connected to form an idle list;
Return chunk, that is, the first idle memory block in the chunk;
}
}
Algorithm: chunk_alloc
Input: size of the memory block, number of pre-allocated memory blocks nobj (passed by reference)
Output: the address of a contiguous memory area and the number of memory blocks that can be accommodated in the area
{
Calculate the total memory size total_bytes;
If (the memory pool is sufficient for allocation, that is, end_free-start_free> = total_bytes ){
Update start_free;
Returns the old start_free;
} Else if (nobj memory blocks are not enough in the memory pool, but at least one can be allocated ){
Calculate the number of memory blocks that can be allocated and modify nobj;
Update start_free and return the original start_free;
} Else {// The Memory Pool cannot be allocated with a memory block.
First, link the memory block of the memory pool to the corresponding free_list;
Call the malloc operation to re-allocate the memory pool, and add total_bytes with a size of 2 timesAdditional volume, Start_free points to the returned memory address;
If (Allocation failed ){
If (there are still idle blocks in 16 idle lists)
Try to recycle 16 idle blocks from the idle list to the memory pool and then call chunk_alloc (size, nobj );
Else {
Whether the out of memory mechanism is still used when the first-level distributor is called;
}
}
Update end_free to start_free + total_bytes, and heap_size to 2 times of total_bytes;
Call chunk_alloc (size, nobj );
}
}
Algorithm: deallocate
Input: memory block address to be released P and size
{
If (size greater than 128 bytes) directly calls free (p) release;
Else {
Set the size to a multiple of 8 and obtain the corresponding free_list_head header pointer in the idle list accordingly;
Adjust free_list_head and link P to the idle list block;
}
}
In this scenario, free_list [2] already points to a 24-byte free block linked list. As shown in figure 1, when you apply for a 21-byte memory block from Allocator, allocaotr checks free_list [2] First, allocates the memory block referred to by free_list [2] to the user, and points the header to the next available idle block, as shown in figure 2. Note: When the memory block is on the linked list, the first four bytes are used to point to the next idle block. When allocated to the user, it is a common memory area.
Figure 1 Allocator status at a certain time
Figure 2 allocate a 24-byte memory block
4. Summary
The memory distributor in STL is actually based on the Free List allocation policy. The main feature is to optimize the allocation of small objects by organizing 16 free lists.
1) Fast allocation and release of small objects. After a fixed memory pool is pre-allocated at a time, the allocation and release operations for small memory blocks smaller than 128 bytes are only some basic pointer operations, compared to directly calling malloc/free, low overhead.
2) avoid Memory fragments. Random memory fragments will not only waste memory space, but also put pressure on the memory management of the OS.
3) Maximize the memory usage as much as possible. When the remaining idle areas in the memory pool are insufficient to allocate the required hours, the allocation algorithm will link them to the corresponding idle list, then, the system will try to find out whether there are suitable regions in the idle list,
However, this memory distributor is only used in STL containers and is not suitable for a general memory allocation. Because it requires that the size of the memory block must be provided when a memory block is released to determine which free list to recycle, and the STL container knows the size of the objects it needs to allocate, for example:
STL: vector <int> array;
Array indicates that the object size to be allocated is sizeof (INT ). A general memory distributor does not need to know the size of the memory to be released, similar to free (P ).
[Switch] STL memory distributor