Chapter 2 Memory Management
The management problem of memory involved in C can be attributed to two aspects: it is correctly obtained and used effectively. A good programmer will understand why these two issues are listed in this order. Because the execution is fast, the program is small if it does not execute it as if you think, it is not used. "Correctly" means correctly calling memory allocation and release programs; "Effectively Using" is a memory allocation and release program that is written a particular version. Here, "correctly" seems to be more important.
However, it is true that C has inherited a very serious headache from C, which is the hidden danger of memory. Virtual memory is a good invention, but virtual memory is limited, not everyone can grab it first.
In C, as long as the memory allocated by Malloc is not returned with Free, memory leaks will be generated. In C , the name of the perpetrator is replaced with new and delete, but the situation is basically the same. Of course, because there is a session of the designer, the situation is slightly improved because the designer function provides a place where the object to be destroyed provides a convenient call. But this also brought more troubles because New and Delete are implicitly modulated constructor and destructive functions. And because the New and Delete operators can be defined within the class and the class, this has brought complexity and increasing an error. The following terms (also Terms M8) will tell you how to avoid the universal problems.
script>
Terms 5: Corresponding New and Delete To use the same form
What is wrong with the following statement?
String * stringArray = new string [100];
...
DELETE STRINGARRAY;
Everything seems to be in order - a New corresponds to a delete, but hidden a lot of mistakes: the operation of the program will be unpredictable. At least, 99 of the 100 String objects pointing to StringArray will not be properly destroyed because their destructor will never be called.
Two things will occur when using New. First, the memory is assigned (via the Operator New function, see Terms 7-10 and Terms M8), then, one or more constructor is called for the assigned memory. When using Delete, there are two things happen: First, to call one or more destructor that will be released, then release memory (through the Operator Delete function, see Terms 8 and M8). There is an important issue for Delete: How many objects do you have to be deleted? The answer determines how many destructor will be called.
This problem is simple to say: The pointer to be deleted is a single object, or an object array? This is only you tell Delete. If you don't use parentheses when you use Delete, Delete will think that a single object, otherwise, it will think is an array:
String * stringptr1 = new string;
String * stringptr2 = new string [100];
...
Delete stringptr1; // Delete an object
Delete [] stringptr2; // delete an array of objects
What will I add "[]" before StringPtr1 "[]"? The answer is: That will be unpredictable; if you didn't add "[]" before StringPtr2? The answer is: unpredictable. And for a fixed type like INT, the result is not predictable, even if such types have no destructive functions. So, the rules that solve such problems are simple: if you call New [], use [] when calling Delete []. If you don't use [] when calling New, you don't use []. Keep in mind this rule when writing a class containing a pointer data member and providing multiple constructors. Because of this, you must use the same new form in the constructor of all initialization pointer members. Otherwise, what form of DELETE will be used in the destructor? Further explanation about this topic, see Terms 11.
This rule is also important for people who like to use Typedef, because the programmers who write TypedEF must tell others, after using NEW to create a type of TypedEf defined type, what form of delete is used to delete. For example, as follows:
Typedef string addresslines [4]; // A person's address, a total of 4 lines, one string each line
// Because AddressLines is an array, use New:
String * PAL = new addressline; // Note "New AddressLines" Returns String *, and
// "New String [4]" is the same
DELETE must be in the form of array:
DELETE PAL; / / Error!
Delete [] PAL; / / correct
In order to avoid confusion, it is best to prevent the array type TypeDefs. This is actually very easy, because the standard C library (see Terms 49) contains Stirng and Vector Templates, using them will reduce the demand for array to almost zero. For example, AddressLines can define a string (vector), ready to be a vector
script>
Terms 6: Calling Delete for pointer members in the destructor
In most cases, the classes that perform dynamic memory allocation are allocated in the constructor, and the memory is allocated with New, and then releases memory with delete in the destructor. When you first write this class, it is of course not difficult, you will remember that all members allocated in all constructed functions use DELETE.
However, this class has been maintained and upgraded, and the situation will become difficult, because the programmer for modification of class code is not necessarily the earliest to write this class. Increases a pointer member means almost all the following work: • Initialize the pointer in each constructor. For some constructor, if there is no memory to be assigned to the pointer, the pointer is initialized to 0 (ie, the null pointer). · Delete existing memory and assign a new memory by assigning the operator. · Remove the pointer in the destructor.
If you forget to initialize a pointer in the constructor, or forget it in the process of assignment, the problem will appear very fast, it is obvious, so these two questions in practice will not torture you. However, if the pointer is not deleted in the destructor, it does not show great external symptoms. Instead, it may only be characterized by a little slight memory leak, and growing, and finally engraving your address space, causing the program to abort. Because this situation is often not so attacked, it must be referred to each time a pointer member is added to the class. In addition, deleting an empty pointer is safe (because it doesn't do anything). So, when writing constructor, assigning operators, or other member functions, each pointer member of the class either points to a valid memory, or point to the empty, then you can use only simple Delete in your destructor. Drop them, don't worry, they are too many.
Of course, don't use the use of this terms. For example, you certainly don't delete a pointer that is not initialized with New, and you will never delete a pointer to you when you use smart to delete it. In other words, unless the class member originally used New, it is not necessary to use Delete in the destructor.
Speaking of intelligent pointers, here is introduced to avoid the method of deleting a pointer member, that is, replacing these members with intelligent pointer, such as Auto_PTR in the C standard library. Want to know how it works, look at the Terms M9 and M10.
script>
Terms 7: Pre-prepared in advance
Operator New will throw an exception when you can't complete memory allocation (former practice is usually returned 0, some older compilers do this. If you want, you can set your compiler to this. About this topic The discussion will be discussed at the end of this Territor. Everyone knows that it is really a moral behavior that the abnormality that deals in memory is really a moral behavior, but it actually acts like a knife holder in the neck. So, sometimes you don't deserve it, maybe I have never deserved it. But your heart must still have a deep hidden sense of sin: What should I do if NEW really produces an abnormally?
You will naturally think of a method of dealing with this situation, that is, go back to the previous old road and use pretreatment. For example, a common practice for c is to define a type-independent macro to allocate memory and check whether allocation is successful. For C , this macro looks like this:
#define new (PTR, TYPE) /
Try {(PTR) = New Type;} /
Catch (std :: bad_alloc); {assert (0);
("Slow! Std :: bad_alloc What is done?" You will ask. BAD_ALLOC is the Operator New Unusual type that does not meet the memory allocation request, STD is the name of the name space (see clause 28) where Bad_alloc is located. " Ok! "You will continue to ask," Assert is useful? "If you look at the standard C header file
New T;
New T (constructor arguments);
New t [size];
The problem is greatly simplified here because some people will also customize (overload) Operator New, so the program will contain any syntax form using the New.
So what should I do? If you want to use a very simple error handling method, you can do this: When the memory allocation request cannot meet, call your pre-specified error handling function. This method is based on a routine, that is, when the Operator New cannot meet the request, call the customer's specified error handler before thrown an exception - generally referred to as a new-handler function. (Operator New is actually complex, see clause 8)
Specify the set_new_handler function when the error handler is handled, which is roughly as defined below in the header file
TYPEF VOID (* new_handler) ();
NEW_HANDLER SET_NEW_HANDLER (New_Handler P) throw ();
It can be seen that new_handler is a custom function pointer type, which points to a function that does not return value without input parameters. Set_new_handler is an input and returns a function of the new_handler type.
The input parameter of the set_new_handler is the pointer to the error handler to call when the Operator NEW assigns memory failure, and the return value is the pointer of the old error handler that is already active before the set_new_handler is not called.
You can use SET_NEW_HANDLER below:
// function to call if operator new can't Allocate ENOUGH MEMORY
Void NomoreMemory ()
{
CERR << "Unable to satisfy Request for Memory / N";
Abort ();
}
int main ()
{
Set_new_handler (NomoreMemory);
INT * PBIGDATAARRAY = New int [100000000];
...
}
If the Operator New cannot be a 100,000,000 integration space, NomoreMemory will be called, and the program is terminated after an error message. This is better than simply generating error messages to generate error messages in the system kernel. (By the way, if CERR wants to allocate memory during writing error information, what will happen ...) When the Operator New does not meet the memory allocation request, the New-handler function does not only call once, but repeats. Until enough memory. Realizing duplicate calls can be seen in terms 8, here I use a descriptive language: a well-designed New-Handler function must implement one of the following functions. · Generate more available memory. This will enable the attempt to assign memory in Operator New to be successful. One way to implement this strategy is to assign a large memory block when the program starts, and then releases when the new-handler is called for the first time. Along with some warning information to the user, if there is too little memory, the next request may fail unless there are more free space. · Install another different new-handler function. If the current New-Handler function does not produce more available memory, it may know that another new-handler function can provide more resources. In this case, the current new-handler can install another new-handler to replace it (by calling set_new_handler). The next time the Operator New is called New-handler, the most recently installed is used. (Another way to change this strategy is to let New-Handler change its own run behavior, then it will make different things when calling, it is to make New-Handler to modify the static status that affects its own behavior. Or global data.) · Remove New-Handler. That is, pass the empty pointer to set_new_handler. No new-handler is installed, the Operator New assigns memory that will throw a standard STD :: Bad_alloc type. • Throw std :: bad_alloc or other types of exceptions inherited from std :: bad_alloc. Such an exception will not be captured by the Operator New, so they will be sent to the place originally performed. (Throw other different types of exceptions will violate the Operator New anomalous specification. The default behavior in the specification is to call Abort, so when New-handler wants to throw an exception, be sure it is from std :: bad_alloc inherited from std :: bad_alloc Want to learn more about the exception regulations, see the Terms M14.) · Nothing returned. Typical practice is to call Abort or Exit. Abort / EXIT can be found in a standard C library (there are standard C libraries, see Terms 49).
The above choice gives you great flexibility in the New-Handler function.
What is the method taken when processing a memory allocation failure, depends on the class of the object to be assigned:
Class x {
PUBLIC:
Static void
OutofMemory ();
...
}
Class y {
PUBLIC:
Static void outofmemory ();
...
}
X * p1 = new x; // If the assignment is successful, call x :: outofmemory
Y * p2 = new y; // If the assignment is unsuccessful, call Y :: OutofMemory
C does not support the NEW-handler function specifically for classes, and it is not necessary. You can implement it yourself, just provide your own version of SET_NEW_HANDAL and OPERATOR New in each class. The class set_new_handler can specify new-handler for class (like a standard set_new_handler to specify the global new-handler). The Operator New guarantees the NEW-handler that replaces the global new-handler when assigning memory-type objects. Assume that the processing class X memory allocation failed. Because the Operator New is assigned memory failure to the type X, the error handler must be called, so you have to declare a static member of a new_handler type in the class. Then the class x looks like this:
Class x {
PUBLIC:
Static new_handler set_new_handler (new_handler p);
Static void * Operator new (SIZE_T SIZE);
Private:
Static new_handler currenthandler;
}
The static members of the class must define outside the class. Because I want to borrow the default initialization value of the static object, I don't start to initialize when defined x :: currenthandler.
New_handler x :: currenthandler; // Default set currenthandler is 0 (ie null)
The set_new_handler function in class X saves any pointer to it and returns any pointer saved before calling it. This is the standard version of SET_NEW_HANDLER:
New_handler x :: set_new_handler (new_handler p)
{
NEW_HANDLER OLDHANDLER = CURRENTHANDLER;
CurrentHandler = P;
Return Oldhandler;
}
Finally, look at the Operator New: 1. Call the standard set_new_handler function, enter the error handler of the parameter is x. This makes X's New-Handler function becomes a global new-handler function. Note that in the following code, the "::" symbol explicitly references the STD space (the standard set_new_handler function exists in the STD space).
2. Call the global operator new allocation of memory. If the first assignment fails, the global Operator New will call the X-Handler because it is just (see 1.) is installed as a global new-handler. If the global Operator New is finally failed to be assigned to memory, it throws std :: bad_alloc exception, X's Operator New will capture it. X of the Operator New then restores the originally replaced global new-handler function, and finally returns an exception.
3. Assume that the global operator new is allocated in memory, and the Operator New will call the criteria set_new_handler again to restore the original global error handling function. Finally, returns a pointer to the successful memory. C is doing this:
Void * x :: Operator new (size_t size)
{
New_Handler GlobalHandler = // Install X_Handler
Std :: set_new_handler (currenthandler);
Void * Memory;
Try {// Try allocated memory
Memory = :: Operator New (size);
}
Catch (std :: bad_alloc &) {// Restore old new_handler
Std :: set_new_handler (globalhandler); throw; // throw an exception
}
Std: set_new_handler (globalhandler); // Restore the old new_handler
Return Memory;
}
If you do not plead eye to the above, you can see the Terms M9 to remove them.
The memory allocation processing function using class X is approximately as follows:
Void NomoreMemory (); // x Object Distribution NEW_HANDLER function when the memory failed
x :: set_new_handler (nomorememory);
// Set NomoreMemory to x
// new-handling function
x * px1 = new x;
/ / If the memory allocation failed,
// Call NomoreMemory
String * ps = new string;
/ / Call the global new-handling function such as memory allocation
x :: set_new_handler (0);
// Set X's new-handling function is empty
X * px2 = new x;
// If the memory allocation fails, immediately throw an exception
// (Class X does not have new-handling functions)
You will notice that the above similar situation, if you don't consider the class, the code is the same, which naturally thinks that you can reuse them elsewhere. As explained in Terms 41, inheritance and templates can be used to design reusable code. Here, we combine both methods to meet your requirements.
As long as you create a base class of "Mixin-style), this base class allows subclasses to inherit its specific function - here is the function of establishing a class of New-handler. The reason for designing a base class is to allow all subclasses to inherit the set_new_handler and Operator New feature, and the design template is to make each subclass have different CurrentHandler data members. This sounds complicated, but you will see the code is actually very familiar. The difference is that it can now be reused by any class.
Template
Class NewHandlersupport {// Mixed Style
PUBLIC:
Static new_handler set_new_handler (new_handler p);
Static void * Operator new (SIZE_T SIZE);
Private:
Static new_handler currenthandler;
}
Template
New_handler newHandlersupport
{
NEW_HANDLER OLDHANDLER = CURRENTHANDLER;
CurrentHandler = P;
Return Oldhandler;
}
Template
Void * NewHandlersupport
{
New_Handler GlobalHandler =
Std :: set_new_handler (currenthandler);
Void * Memory;
Try {
Memory = :: Operator New (size);
}
Catch (std :: bad_alloc) {
std :: set_new_handler (globalhandler);
Throw;
}
Std :: set_new_handler (globalhandler); returnemory;
}
// this sets Each CurrentHandler TO 0
Template
New_Handler NewHandlersupport
With this template class, it is very simple to add the set_new_handler function on class x: Just let X from NewHandlersupport
// Note Inheritance from Mixin Base Class Template. (See
// my article on counting objects for information on why
// private inheritance might be preference here.)
Class x: public newHandlersupport
... // AS Before, But No Declarations for
}; // set_new_handler or operator new
Still don't care about what it is doing after using X; the old code is still working. This is good! Those who you often don't want to pay often is often the most trustworthy.
Using SET_NEW_HANDLER is a convenient, simple method for processing insufficient memory. It is certainly much better than packing each New in the TRY module. Moreover, NewHandlersupport makes it easier to add a specific New-handler to any class. The inheritance of "mixed style" is inevitably introduced to inherit the topic. Before transferring to this topic, you must read the terms 43.
Before 1993, C has been asked to return 0 when the memory allocation fails, and now the Operator New throws std :: Bad_alloc exceptions. Many C programs are written in front of the compiler to support new specifications. The C Standards Committee does not want to give up the code that has followed the 0 specification, so they provide additional form Operator New (and Operator New [] - see Terms 8) to continue to provide the return 0 function. These forms are called "no throws" because they have not used a throw, but in the use of Nothrow objects with the entry point of New:
Class widget {...};
Widget * pw1 = new widget; // Assignment failed to throw std :: bad_alloc
IF (PW1 == 0) ... // This check must fail
Widget * pw2 = new (nothrow) widget; // If the assignment fails to return 0
IF (PW2 == 0) ... // This check may succeed
Regardless of the "formal" (ie, throwing anomalies), New is still "no throw" form, is important, you must prepare for memory allocation failure. The easiest way is to use SET_NEW_HANDLER because it is useful for both forms.
script>
Terms 8: Write Operator New and Operator Delete to follow regular
When you rewrite Operator New (Terms 10 explains why sometimes to rewrite it), it is important to be the behavior provided by the function and the system default Operator New consistent. It is actually what is also: there is a correct return value; the available memory is not enough to call the error handler (see Terms 7); process the 0 byte memory request. In addition, it is necessary to avoid accidentally hidden standard forms, but this is the topic of clause 9. The part of the return value is simple. If the memory allocation request is successful, return a pointer to the memory; if it fails, follow the provisions of the clause 7 throw a std :: Bad_alloc type exception.
But things are not so simple. Because the Operator New actually tries to assign memory at a time, it is necessary to call an error handler after each failure, and it is also desirable that the error handler can find ways to release the memory. The Operator New throws an exception if the pointer to the error handler is empty.
In addition, the C standard requires that the Operator New is also returned to a legitimate pointer even when the 0 byte memory is requested. (In fact, this sounds are strange, it is easy to bring easy to C language elsewhere)
In this way, the pseudo code of the Operator New in the form of a non-class member looks like this: void * operator new (size_t size) // Operator New may have other parameters {
If (size == 0) {// Treat 0 byte request, size = 1; // Treat it as a one byte request} while (1) {assign SIZE byte memory;
IF (allocated successfully) Return (pointing to memory);
// Distribution is unsuccessful, find the current error handage function new_handler globalhandler = set_new_handler (0); set_new_handler (GlobalHandler);
IF (GLOBALHANDAL) (* GlobalHandler) (); else throw std :: bad_alloc ();}}
The skill of processing the zero-byte request is to process it as a request byte. This looks very blame, but simple, legal, effective. Moreover, how long will you encounter a zero-byte request?
You will be strange why the error processing function is set to 0 after the pseudo code above. This is because there is no way to get a pointer to the error handling function, so you must find it by calling set_new_handler. The way is very stupid but is also effective.
Terms 7 mentioned that the Operator New includes an infinite loop. The above code clearly shows this point --While (1) will result in an infinite loop. The only way to jump out the loop is that the memory allocation is successful or the error handler completed one of the events described in Terms 7: Get more available memory; installed a new new-handler (error handler); unload New-handler; throw a std :: Bad_alloc or its derived type of exception; or return fails. Now I understand why new-handler must do one of these work. If not, the loop inside Operator New will not end.
Many people don't realize that Operator New often inherits by subclasses. This can lead to certain complexity. In the pseudo code above, the function will be assigned the SIZE byte memory (unless size is 0). SIZE is important because it is the parameter passed to the function. However, most of the Operator New (including Terms 10) written for classes is only designed for a particular class, not for all classes, nor for all of its subclass. This means that for a class X's Operator New, the behavior inside the function involves the overall size of the object, is accurate SIZEOF (X): Will not be large. However, due to the existence of inheritance, the Operator New in the base class may be called to allocate memory for a sub-class object: class base {public: static void * operator new; ...}; class derived: public Base / / Derived class has no statement Operator new {...}; //
Derived * p = new derived; // Call Base :: Operator New
If the Base class's Operator New does not want to pay for this situation - this situation is not possible - the easiest way is to transfer this "wrong" memory allocation request to standard operator new Processing, like this: void * base :: operator new (size! = Sizeof (base)) // If the number "error", the standard operator new return :: Operator new (size); / / Go to handle this request //
... // Otherwise handle this request}
"Stop!" I heard you calling, "You forgot to check a situation although it is unreasonable but there may be anything - Size may be zero!" Yes, I didn't check, but please call next time. Don't be such a literary. :) But actually check or do it, but integrated into the Size! = SizeOf (Base) statement. The C standard is very weird, one of which is to specify that the independent (free "class is a non-zero value. So SizeOf (Base) can never be zero (even if the base class is not a member), if size is zero, the request will go to :: Operator New, which is processed in a reasonable way. (Interestingly, if Base is not an independent class, sizeof (base) may be zero, detailed see "My Article on Counting Objects").
If you want to control the memory allocation based on the class-based array, you must implement the array of Operator New. ). When writing Operator New [], you should remember that you face "original" memory, you cannot do anything about objects that don't exist in arrays. In fact, you don't even know how many objects in the array, because I don't know how big each object is. The Operator New [] of the base class is used to allocate memory as an array of sub-objects in the manner, while sub-class objects are often larger than base categories. So, I can't think of that the size of each object in Base :: Operator New [] is sizeof (base), that is, the number of objects in the array is not necessarily (request byte) / sizeof (base). See the Terms M8 for details on Operator New []. Override Operator New (and Operator New []) All regular regulations are these. For Operator Delete (and its partner Operator Delete []), the situation is simpler. What you have to remember is that C guarantees that the empty pointer is always safe, so you have to fully apply this guarantee. Here is the pseudode code of Operator delete in the form of non-class members: void Operator delete (void * rawMemory) {if (rawMemory == 0) return; file: // If the pointer is empty, return //
Release RawMemory points to the memory;
Return;}
The class member version of this function is also simple, but it must also check the size of the object being deleted. Assuming the class's Operator New forwards the "error" size assignment request to :: Operator New, then the "Error" size must be transferred to :: Operator Delete:
Class base {//, the same as front, just declare public: // Operator delete static void * operator new; static void operator delete (void * rawMemory, size_t size); ...};
Void Base :: operator delete (void * rawMemory, size_t size) {if (rawMemory == 0) Return; // Check the empty pointer
IF (size! = sizeof (base)) {// If the size "error", :: operator delete (RawMemory); // Let the standard operator to process the request Return;}
Release pointing to RawMemory;
Return;}
It can be seen that the rules about Operator New and Operator delete (and their array form) are not so troublesome, and it is important to follow it. As long as the memory allocation supports the New-Handler function and correctly handles the zero memory request; if the memory release program handles the empty pointer, then there is nothing else to do. As for adding inheritance support in the function of the class member version, it will soon be completed.
script>
Terms 9: Avoid hidden standard forms of New
Because the name of the internal range declaration hides the same name of the external range, the class's member function hides the global function for the function f of the two identical names of the internal and global declarations, respectively.
Void f (); // global function
Class x {public: void f (); // member function};
X x;
f (); // call f
X.f (); // call x :: f
This will not be surprising, it will not cause confusion, because calling global functions and member functions always adopting different
Grammatical form. However, if you add an Operator new function with multiple parameters in the class,
It may be shocked.
Class x {public: void f ();
// Operator new parameters Specify a // new-hand (error handling) function static void * Operator new (size_t size, new_handler p);};
void specialerrorhandler (); // Define in other places
X * px1 = new (SpeciaLerrorhandler) x; // call x :: Operator new
X * px2 = new x; // error!
After a function called "Operator New" is defined in the class, it is not intentionally stopped from accessing the standard New.
ask. The clause 50 explains why this is, here we are more concerned about how to think of a way to avoid this problem.
One way is to write an Operator New that supports standard New calls in the class, which is the same as the standard New
Do things. This can be encapsulated with an efficient inline function.
Class x {public: void f ();
Static void * Operator new (size_t size, new_handler p);
Static void * operator new (size_t size) {return :: operator new (size);}}
X * px1 = new (SpeciaLerRhandler) x; // Call x :: operator // new (size_t, new_handler)
x * px2 = new x; // Call x :: Operator // new (size_t)
Another method is to provide default values for each parameter added to Operator New (see Terms 24):
Class x {public: void f ();
Static void * operator new (size_t size, // p default is 0 new_handler p = 0); //};
X * px1 = new (SpeciaLerRhandler) x; // correct
x * px2 = new x; // is also correct
No matter which method, if you want to customize new features in the "standard" form, you only need to override this function. The call can use new features after recoiling the link.