C learning notes -- malloc Memory Allocation
In view of the solution that the previous leader told me to write it as a document, and I knew how to operate and solve it in my mind, but I did not know how to describe it in words. I decided to write more notes and microblogs to train my writing skills and cultivate logical thinking in the future. Otherwise, it would only be code-based and never go to the management level.
After reading the C programming language carefully, go to Chapter 2 the last two sections of UNIX system interfaces-"directory list" and "Storage Allocation program ", I did not understand it once. IQ is not high. After reading the storage allocation again, I got my brow. In the past two days, you have to find time to read the directory list again to ensure that you have mastered it. (I have read many times in some of the previous chapters, and I am confused. I just asked Google Baidu for help)
The storage allocation program here refers to the implementation principle of the malloc function in the standard library. First, you need to understand the memory storage structure for malloc. Unlike global variables, malloc does not allocate memory space during compiler compilation, but is allocated only when the malloc function is called. Sometimes the free function is called halfway to release the space. Therefore:
1. When malloc is called for the first time, obtain the free space of a minimum unit from the system (for example, the minimum unit is 1024 restricted unit blocks. If x <= 1024, get 1024; otherwise, Get x), and then allocate;
2. The remaining free space of malloc is not sequential, but scattered. This also improves the space utilization.
To manage the free space of malloc, each independent block contains a "Header" at the beginning: A pointer pointing to the next idle block and the length of an independent block (the book says there is a pointer pointing to its own bucket, but each bucket has its own pointer, why. Later I read the original English version. I wrote the Each block contains a size, a pointer to nextblock, and the space itself .). The next idle block is an idle block that is closest to the current idle block in ascending order of the storage address. If the current idle block is at the end, it points to the first idle block. In this way, all the idle space belonging to malloc is chained together. As shown in:
Note: The Chinese version of this image indicates that the translation is incorrect, for example:
Because the following free function has integrated adjacent idle linked lists into one, there is no adjacent idle linked list in my diagram.
Each space controlled by malloc contains a "Header". To facilitate management, the size of each space is an integer multiple of the header size. The Header Length = the length of the pointer pointing to the next idle block + the unsigned length of its own space. However, to ensure that the storage space returned by the malloc function meets the alignment requirements of the objects to be saved. Each machine has one of the most restrictive types: if the most restrictive type can be stored in a specific address, all other types can also be stored in this address. Some are double, some are long, and even some are int. Therefore, the header structure is associated with the most restrictive type to ensure alignment.
Because there is header information, the size of the block space in the header information also includes the size of the header. Therefore, each time you apply for malloc idle blocks, you must add a unit, finally, remove the header when returning it to the user.
Every time you call malloc to apply for a space, malloc has a static pointer freep dedicated to the current idle block linked list. Scan the remaining idle block linked list from the current start until a large enough idle block is scanned. This algorithm becomes "first fit", in contrast to "best fit": it will scan the smallest block that meets the conditions. The code here is the first adaptive algorithm. There are three possible results:
1) if a suitable idle block is found, the space is removed from the linked list, the address of the block is returned to the user, and the static pointer freep is directed to the previous idle block address;
2) find a free block that is larger than the demand, and then take a space at the back of the free block to the user, and change the free block at the front;
Note (I did not find myself wrong until I wrote this blog ):
① It has always been considered that the header of the previous unit should be returned to the user address, and the space should be returned to the previous idle block, otherwise it will be idle. In fact, it records an important information: the size of the space block (including the size of the header and the size of the unit returned to the user). This header must be used when free space is released;
② The following free program is designed for the system to apply for space and insert the idle block linked list. In fact, it is a program that we usually use malloc, realloc, or calloc to apply for space and then release it.
3) If no idle block is found after scanning, apply for a new space from the system.
All of the above are on the premise that malloc already has idle blocks, but at the first application, malloc does not have free block space. Therefore, during pre-compilation, an idle block linked list base of a unit is established to serve as the entry to the idle linked list. When malloc is called for the first time, the static pointer freep of the idle linked list is NULL, which points to the base and the size is set to 0 (in this way, the base space will always exist and will not be applied, ensure that freep always points to a valid idle block linked list), points to itself, and applies for free space from the system at the same time (each time the space requested from the system is a continuous idle block ).
Typedef long Align;/* alignment according to the long type boundary, that is, using long as the most restrictive type */union header {/* header information */struct {union header * ptr; /* point to the next idle block */unsigned size;/* idle block size */} s; Align x;/* force alignment */}; typedef union header; static Header base;/* the first time the idle block Linked List entry of malloc is called, the empty linked list with a size of 0 (according to the above logic, the size here should be 1) */static Header * freep = NULL;/* static idle block linked list pointer, initialized to NULL. After the first application, it will point to the base * // * malloc function: common storage allocation function */void * malloc (unsigned nbytes) {Header * p, * prevp; /* defines an idle block pointer variable and the previous idle block pointer variable */Header * morecore (unsigned);/* applies for the idle space function */unsigned nunits from the system; /* Actual unit size to be applied for, that is, z */nunits = (nbytes + sizeof (Header)-1)/sizeof (Header) + 1; /* converts the byte size to the unit size, rounded up, and adds a unit (header) */if (prevp = freep) = NULL) {/* No idle linked list, first application */base. s. ptr = prevp = freep = & base;/* freep points to base, and the next idle block pointer of base points to itself */base. s. size = 0;/* set the size to 0 */} for (p = prevp-> s. ptr; prevp = p, p = p-> s. ptr) {if (p-> s. size> = nunits) {if (p-> s. size = nunits)/* the size is right */prevp-> s. ptr = p-> s. ptr;/* remove the free zone */else {/* greater than the actual requirement. Allocate */p-> s from the end of the free zone. size-= nunits;/* reduce the size of idle blocks */p + = p-> s. size;/* pointer to the header of the requested space */p-> s. size = nunits;/* set the size of the idle block to be applied */} freep = prevp;/* The current static Pointer Points to the previous idle block. If the current block has an idle area, the next scan will continue from here, saving time */return (void *) (p + 1 ); /* return the idle space of the unheader Unit */} if (p = freep)/* Closed-Loop idle linked list. Call malloc application for the first time or scan it again, no space is found */if (p = morecore (nunits) = NULL)/* apply for space from the system */return NULL;/* The application is not successful, */}}
According to the program analysis of the following morecore () and free () functions, after successfully applying for a space from the system, p points to a free block with sufficient space. However, in this Code, before entering the next idle block scan, p points to the next insufficient idle block, resulting in multiple scans. In my opinion, if the space is sufficient, I can apply for another static pointer beforefreep, pointing to the previous idle block of freep. The above code can be added to improve efficiency:
If (p = freep) {/* Add braces */if (p = morecore (nunits) = NULL) return NULL; p = beforefreep ;} or if (p = freep)/* you do not need to increase the brackets here. else matches with the latest if */if (p = morecore (nunits) = NULL) return NULL; elsep = beforefreep;
When applying for space from the system, there is not a pay-as-you-go allocation, but a minimum number of application tickets. This will allow you to use it enough. You don't have to apply to the system every time, and the system will not waste space.
To apply for a space from the system, you also need to call the system call sbrk (n) (under UNIX). If the application is successful, the pointer returns a bucket pointing to n Bytes. If the application fails, -1 (not NULL) is returned ). The returned pointer type is char * (should be the smallest unit of storage space ).
# Define NALLOC 1024/* Minimum number of application tickets * // * morecore function: Apply for more storage space from the system */static Header * morecore (unsigned nu) /* returns the pointer to the static idle block linked list */{char * cp, * sbrk (int); Header * up; if (nu <NALLOC) nu = NALLOC; cp = sbrk (nu * sizeof (Header);/* call the system call request system space */if (cp = (char *)-1) return NULL; /* application failed, no space */up = (Header *) cp;/* convert to Header * pointer type */up-> s. size = nu;/* set the size of the space block */free (void *) (up + 1);/* release space */return freep ;}
Here, the returned freep is updated in free before it is returned. I also thought that since freep is a static global variable, why should I return a static variable here? Just assign a value directly in the function. In fact, there are successes and failures, so the program needs to judge the application results, and the returned freep is the most relevant thing to apply.
The free (void * ap) function is to release the space indicated by the ap. The specific size of the space to be released is in the first pointer of the ap, that is, the header information. The primary purpose of release is to insert the space into the idle block linked list. Therefore, find the free block on both sides of the space block (or there may be only one free block, that is, the entry base ). Then determine whether it is connected to the previous one. If it is connected to the next one, it is merged into one. Otherwise, a new idle block linked list is inserted directly in the middle.
/* Free function: Release the ap and put the ap block into the idle linked list */void free (void * ap) {Header * p, * bp; bp = (Header *) ap-1;/* points to the header of the ap block */for (p = freep ;! (Bp> p & bp <p-> s. ptr); p = p-> s. ptr)/* locate the position in the idle linked list where bp is located */if (p> = p-> s. ptr & (bp> p | bp <p-> s. ptr)/* determine whether it is at the beginning or end of the linked list */break; if (bp + bp-> s. size = p-> s. ptr) {/* first judge whether it can be merged with the idle block of the high address, that is, merged with the next block */bp-> s. size + = p-> s. ptr-> s. size; bp-> s. ptr = p-> s. ptr-> s. ptr;} elsebp-> s. ptr = p-> s. ptr;/* cannot be merged. bp points to the next address */if (p + p-> s. size = bp) {/* Then judge whether it can be merged with the idle block of the local address, that is, merged with the previous block */p-> s. size + = bp-> s. size; p-> s. ptr = bp-> s. ptr;} elsep-> s. ptr = bp;/* cannot be merged. p points to the bp address */freep = p ;}
Note: The Chinese translation is ambiguous. The original works are "join to upper nbr" and "jointo lower nbr ".
This free program is a wonderful solution. General Thinking: First merge with the previous one, and then merge with the next one. The following procedure (obviously better than mine ):
If (p + p-> s. size = bp) {/* is connected to the previous one? */If (bp + bp-> s. size = p-> s. ptr) {/* is connected to the next one? */P-> s. size + = bp-> s. size + p-> s. ptr-> s. size; p-> s. ptr = p-> s. ptr-> s. ptr;} elsep-> s. size + = bp-> s. size;} else {if (bp + bp-> s. size = p-> s. ptr) {/* is connected to the next one? */Bp-> s. size + = p-> s. ptr-> s. size; bp-> s. ptr = p-> s. ptr-> s. ptr; p-> s. ptr = bp;} else/* not connected to any part */bp-> s. ptr = p-> s. ptr; p-> s. ptr = bp ;}
It can be seen from this that the space applied through malloc is not initialized, so remember to initialize it before use and use it as the right value accidentally. The probability of an error is high.
C programming language (version 2nd-the new version) is indeed a classic and worthy of careful reading and consolidation. Thank you!
I 've been writing it for a long time. If something is wrong or bad, you are welcome to correct and criticize it! Thank you for taking the time to read this article!