New and delete are basic interfaces for memory allocation and release in C ++,ProgramThe management of memory by Members relies on these two interfaces.
Since the C ++ compiler already provides the new and delete interfaces, why do we recreate the new and delete interfaces.
This raises the first question,Under what circumstances do we need to re-customize new and delete?(It must be when the new and delete provided by the compiler meet the requirements)
1. Used to Detect application errors
During programming, "overruns" (after the end of the allocated block) or "underruns" (before the start point of the allocated block) may occur ). if we define an operator news, we can overallocate the memory and place the specified byte patterns with extra space (before or after the customer's block. operator deteles can check whether the above signature has not changed. If not, it indicates that overrun and underrun occurred at a certain time point in the allocation area. At this time, operator Delete can record this fact.
2. To improve efficiency
Various applications have different requirements for the Memory Manager. The operator news and operator deletes provided by the compiler take a moderate approach to satisfy various allocation patterns, their work is moderately good for everyone, but they do not have the best performance for any specific needs. if you have a deep understanding of the dynamic memory usage type of the program and customize operator new and operaotr Delete, the performance can be better than the default.
3. to collect statistical data during use
Before making operator new and operator Delete, we must understand how the program uses dynamic memory. How is the size distribution of allocated blocks? What is the life cycle distribution? They tend to be allocated and returned in FIFO or LIFO or random order. To collect this information, we customize operator new and operator Delete.
4. To increase the allocation and return speed
The generic distribution is often slower than the custom distribution, especially when the custom distribution is designed for a specific type of object. of course, before proceeding to the conclusion that "operator new" and "operator Delete" have the value of accelerating the program speed, we must analyze the program to confirm that the program bottleneck does occur on those memory functions.
5. To reduce extra space overhead caused by default Memory Manager
Generic splitters often use more memory, especially when allocating small objects, which may lead to a waste of resources because they often introduce some additional overhead on each allocation block. the Allocator developed for small objects can eliminate such extra overhead.
6. To make up for the non-optimal position in the default allocation
In the X86 architecture, doubles is the fastest to access, if they are all 8-byte homogeneous. however, operator news provided by the compiler does not guarantee that 8-bytes is used for the dynamically allocated doubles. in this case, the default operator new is replaced with an 8-byte homogeneous guaranteed version, which can greatly improve the program efficiency.
7. To make related objects into a cluster
If we know that a specific data structure is often used together, and we want to minimize the frequency of "Memory Page errors" when processing the data, we can create another heap for some data structures so that they can be concentrated on as few memory pages as possible.
8. To obtain nontraditional Behaviors
Sometimes we need operator news and deletes to do something that the compiler does not. for example, allocate and return the blocks in the shared memory, but the only function that can manage the memory is the c API function. Therefore, write a customized new and delete, add a C ++ coat Based on the c api.
Now that we know the time to customize new and delete, we have to start to customize new and delete.
Before customizing new and delete, we need to understand the new and delete mechanisms provided by the compiler, which is equivalent to looking at the current wheel before creating our own wheel and referring to the existing implementation.
The specific implementation is not described in detail here.New-handler Processing Mechanism.
New provided by the compiler, with insufficient memory, provides its own processing mechanism. before operator new throws an exception to reflect an unfulfilled memory requirement, it calls an error processing function specified by the customer, new-handler.
When we customize new and delete, we also need to provide corresponding processing mechanisms. We can use set_new_handler to specify our own error processing functions. The specific usage is as follows:
1 Namespace STD { 2 Typedef Void (*New_handler )(); 3 New_handler set_new_handler (new_handler P) Throw (); 4 } 5 6 Void Outofmem () 7 { 8 STD: cerr < " Unable to satisfy request for memory \ n " ; 9 STD: Abort (); 10 } 11 Int Main () 12 { 13 STD: set_new_handler (outofmem ); 14 Int * Pbigdataarray = New Int [ 000000000l ]; 15 ... 16 }
The key is how to implement the error processing function to better meet our needs.A well-designed new-handler function must do the following::
1. Make more memory available.
2. install another new-handler.
3. Remove New-handler.
4. Throw an exception of bad_alloc (or derived from bad_alloc.
5. Do not return. usually call abort or exit.
Although we can use set_new_handler to specify an error handler to handle memory allocation failures in different ways.
However, every call to set_new_handler is a global error handling function. When we need to set different error handling functions for objects of different classes, we need to call set_new_handler multiple times.
In addition, to ensure that the default error processing function is called, we must save the default error processing function before setting different error processing functions, to reset the original status.
How can we ensure that the memory allocation fails to be processed in different ways? The memory processing method depends on the object type, that is, the class exclusive New-handlers.
The following describes how to implement the class-specific new-handler.
Implementation ideas:
Each class is provided with its own set_new_handler and operator new. Specifically, set_new_handler allows you to specify the New-handler exclusive to the class,Operator new makes sure that the Global New-handler is replaced with the class-specific new-handler when the class Object Memory is allocated.
Operator new does the following:
1. Call the standard set_new_handler to install the dedicated new-handler of the class as global new-handler.
2. Call global operator new to execute the actual memory allocation. If the allocation fails, global operator new will call the dedicated new-handler of the class.
If the global operator new cannot allocate enough memory, a bad_alloc exception will be thrown.In this case, you need to ensure that the original New-handler can be restored, so the new-handler is managed by the object.
The restoration process of new-handler is completed by the class destructor. When the object is out of scope, it calls the destructor to restore the original New-handler.
1 Class Newhandlerholder { 2 Public : 3 Explicit Newhandlerholder (STD: new_handler NH) 4 : Handler (NH ){} 5 ~ Newhandlerholder () 6 { 7 STD: set_new_handler (handler ); 8 } 9 Private : 10 STD: new_handler handler; 11 Newhandlerholder ( Const Newhandlerholder & ); 12 Newhandlerholder & Operator = ( Const Newhandlerholder & ); 13 }; 14 Template <typename T>15 Class Newhandlersupport { 16 Public : 17 Static STD: new_handler set_new_handler (STD: new_handler P) Throw (); 18 Static Void * Operator New (STD: size_t size)Throw (STD: bad_alloc ); 19 ... 20 Private : 21 Static STD: new_handler currenthandler; 22 }; 23 Template <typename T> 24 STD: new_handler 25 Newhandlersupport <t >:: set_new_handler (STD: new_handler P) Throw () 26 { 27 STD: new_handler oldhandler = Currenthandler; 28 Currenthandler = P; 29 Return Oldhandler; 30 } 31 32 Template <typename T> 33 Void * Newhandlersupport <t> :: Operator New (STD: size_t size) 34 { 35 Newhandlerholder H (STD: set_new_handler (currenthandler )); 36 Return :: Operator New (Size ); 37 } 38 39 Template <typename T> 40 STD: new_handler newhandlersupport <t >:: currenthandler = 0 ; 41 42 Class Widget: Public Newhandlersupport <widget> { 43 ... 44 };
After learning about the new and delete implementation mechanisms of the compiler, we need to start customizing new and delete.
In the customization process, in order to ensure the consistency of the customized new and delete, it is necessary to ensure that it complies with some rules.
1. Operator new must return the correct value. When the memory is insufficient, the new-handling function must be called.
2. Operator new must be prepared to deal with zero memory requirements.
In operator new provided by the compiler, even if the customer requires 0 bytes, operator new must return a valid pointer. Custom new must also properly handle the 0byte requirement.
3. Operator new must be used to call the base class operator new in the derived class.
The solution is to change the calling behavior of memory application errors to the standard operator new.
1 Void* Base ::Operator New(STD: size_t size)Throw(STD: bad_alloc)2 {3If(Size! =Sizeof(Base ))4Return::Operator New(Size );5 ...6}
4. Operator delete must ensure that "deleting null pointers is always safe ".
1 Void Base :: Operator Delete ( Void * Rawmemory, STD: size_t) Throw () 2 { 3 If (Rawmemory = 0 ) Return ; 4 If (Size! =Sizeof (Base )) 5 { 6 :: Operator Delete (rawmemory ); 7 Return ; 8 } 9 ... 10 Return ; 11 }
In addition, pay attention to the customization of new and deleteThread security issues.
Because heap is a global resource that can be modified, the multi-threaded system is flooded with race conditions (race speed status) for accessing such resources.