Chapter 2 Memory Management

Source: Internet
Author: User
Tags class operator what operator
Chapter 2 Memory Management

 

The memory management problems involved in C ++ can be attributed to two aspects: correct and effective use of it. Good programmers will understand why these two questions should be listed in this order. Because a program that runs fast and small is useless if it is not executed as you want. "Get right" means to call the memory allocation and release program correctly, while "use effectively" means to write the memory allocation and release program of a specific version. Here, it is more important to "get it right.

However, speaking of correctness, C ++ actually inherits a very serious headache from C, that is, memory leakage. Virtual Memory is a good invention, but virtual memory is also limited, and not everyone can grab it first.

In C, as long as the memory allocated with malloc is not returned with free, memory leakage will occur. In C ++, the name of the perpetrator is changed to new and delete, but the situation is basically the same. Of course, with the appearance of destructor, the situation is slightly improved, because the Destructor provides a convenient place to call delete for all objects to be destroyed. But at the same time, this brings more troubles, because new and delete call constructor and destructor implicitly. Moreover, the new and delete operators can be customized within and outside the class, which brings complexity and increases the chance of errors. The following clause (and M8) will tell you how to avoid common problems.

Clause 5: The New and delete operations must be in the same format.

 

What are the following statements?

 

string *stringarray = new string[100];...delete stringarray;

Everything seems to be in an orderly manner-a new corresponds to a delete-but there is a large hidden error: the running status of the program will be unpredictable. At least 99 of the 100 string objects pointed to by stringarray will not be correctly destroyed, because their destructor will never be called.

Two things will happen when new is used. First, the memory is allocated (by using the operator new function, see terms 7-10 and M8), and then one or more constructors are called for the allocated memory. When using Delete, there are also two things: first, call one or more destructor for the memory to be released, and then release the memory (through the operator delete function, for details, see clause 8 and M8 ). For Delete, there is an important question: how many objects in the memory will be deleted? The answer determines how many destructor will be called.

Simply put, the pointer to be deleted points to a single object or an object array? Only you can tell Delete. If you use delete without parentheses, delete will point to a single object. Otherwise, it will point to an array:

 

String * stringptr1 = new string; string * stringptr2 = new string [100];... delete stringptr1; // delete an object Delete [] stringptr2; // delete an object Array

What if you add "[]" before stringptr1? The answer is: it will be unpredictable. What if you didn't add "[]" before stringptr2? The answer is: unpredictable. In addition, the results are unpredictable for fixed types such as int, even if such types do not have destructor. Therefore, the rule for solving such problems is simple: if [] is used when you call New, [] is also used when you call Delete. If [] is not used when new is called, [] is not used when Delete is called.

This rule is especially important when writing a class that contains pointer data members and provides multiple constructors. In this case, you must use the same new form in all constructors that initialize pointer members. Otherwise, what form of Delete will be used in the Destructor? For more information about this topic, see clause 11.

This rule is also important for people who like to use typedef, because programmers who write typedef must tell others that after creating a typedef-Defined Object with new, the form of Delete to delete. Example:

 

Typedef string addresslines [4]; // a person's address. There are 4 rows in total. Each line has a string // because addresslines is an array, use new: string * pal = new addresslines; // note that "New addresslines" returns string *, which is the same as that returned by // "New String [4]". Delete pal must correspond to it in array format; // error! Delete [] pal; // correct

To avoid confusion, we recommend that you do not use typedefs for array types. This is actually very easy, because the standard C ++ Library (see cla49) contains stirng and vector templates, using them will reduce the need for arrays to almost zero. For example, addresslines can be defined as a string vector, that is, addresslines can be defined as a vector <string> type.

Clause 6: Call Delete For pointer members in the destructor

 

In most cases, classes that execute dynamic memory allocation use new to allocate memory in the constructor, and then delete to release memory in the destructor. It is certainly not difficult to write this class at the beginning. You will remember to use delete for all the members allocated with memory in all constructors.

However, after the class is maintained and upgraded, the situation becomes difficult, because the programmer who modifies the class code is not necessarily the first person to write the class. Adding a pointer member means almost all of the following work is required:
· Initialize the pointer in each constructor. For some constructors, if there is no memory to allocate to the pointer, the pointer should be initialized to 0 (that is, a null pointer ).
· Delete the existing memory and assign it to the new memory of the pointer through the value assignment operator.
· Delete the pointer in the destructor.

If you forget to initialize a pointer in the constructor or forget to process it during the value assignment operation, the problem will appear very fast and obvious, in practice, these two problems will not affect you. However, if the pointer is not deleted in the destructor, it does not show obvious external symptoms. On the contrary, it may only show a small amount of memory leakage, and continues to grow. In the end, it swallowed up your address space, leading to program failure. Because this situation is often less noticeable, you must note it clearly when adding a pointer member to the class.

In addition, deleting a null pointer is safe (because it does not do anything ). Therefore, when writing constructors, value assignment operators, or other member functions, each pointer member of the class either points to a valid memory or points to null, in your destructor, you can simply delete them without worrying about whether they have been replaced by new ones.

Of course, do not use these terms absolutely. For example, you certainly won't use Delete to delete a pointer that does not use new for initialization, and you don't have to delete it when using smart pointer objects, you will never delete a pointer that is passed to you. In other words, unless the class members use new, they do not need to use Delete in the destructor.

Speaking of smart pointers, this article introduces a method to avoid having to delete pointer members, that is, replacing these Members with smart pointer objects, such as auto_ptr in the C ++ standard library. If you want to know how it works, check the terms M9 and M10.

Clause 7: Prepare insufficient memory in advance

 

Operator new throws an exception when the memory allocation request cannot be completed (in the past, 0 is generally returned, and some earlier compilers still do this. You can set your compiler to this way if you like. I will postpone the discussion to the end of these terms ). As we all know, dealing with exceptions caused by insufficient memory can be regarded as a moral behavior, but it will be as painful as the rest of the neck. So sometimes you don't care about it, maybe you never care about it. But you must have a deep sense of guilt hidden in your mind: What should you do if new really has an exception?

You will naturally think of a way to deal with this situation, that is, back to the Old Road and use preprocessing. For example, a common practice of C is to define a type-independent macro to allocate memory and check whether the allocation is successful. For C ++, this macro may look like this:

 

#define new(ptr, type)/try { (ptr) = new type; }/catch (std::bad_alloc&) { assert(0); }

("Slow! STD: What does bad_alloc do ?" You will ask. Bad_alloc is the exception type thrown when operator new cannot meet the memory allocation request. STD is the name of the namespace where bad_alloc is located (see article 28. "Good !" You will continue to ask, "What is the use of assert ?" If you look at the Standard C header file <assert. h> (or use the namespace version <cassert>, which is equal to its price, as shown in Clause 49), assert is a macro. This macro checks whether the expression passed to it is non-zero. If it is not a non-zero value, an error message is sent and abort is called. Assert does this only when the standard macro ndebug is not defined. In the product release status, when ndebug is defined, assert does nothing, which is equivalent to an empty statement. Therefore, you can only check assertion during debugging )).

The new macro not only has the common problem mentioned above, that is, it uses assert to check the status that may occur in a published Program (however, there may be insufficient memory at any time). At the same time, it also has another defect in C ++: it does not take into account the various usage methods of new. For example, to create a type T object, there are generally three common syntax forms. You must handle all possible exceptions in each form:

 

new t;new t(constructor arguments);new t[size];

The problem is greatly simplified here, because someone will also customize (reload) operator new, so the program will contain any new syntax format.

So what should we do? If you want to use a very simple error processing method, you can do this: when the memory allocation request cannot meet, call an error processing function you specified in advance. This method is based on a general rule, that is, when operator new cannot meet the request, it will call an error processing function specified by the customer before throwing an exception -- generally called the New-handler function. (Operator new must be more complex in actual work. For details, see section 8)

The set_new_handler function is used to specify the error handling function. It is defined as follows in the header file <New>:

 

typedef void (*new_handler)();new_handler set_new_handler(new_handler p) throw();

New_handler is a custom function pointer type that points to a function without input parameters or return values. Set_new_handler is a function of the new_handler type input and returned.

The input parameter of set_new_handler is the pointer to the error processing function to be called when operator new fails to allocate memory. The returned value is the pointer to the old error processing function that has been in effect before set_new_handler is called.

You can use set_new_handler as follows:

 

// function to call if operator new can't allocate enough memoryvoid nomorememory(){cerr << "unable to satisfy request for memory/n";abort();}
int main(){set_new_handler(nomorememory);int *pbigdataarray = new int[100000000];...}

If operator new cannot allocate space for 100,000,000 integers, nomorememory will be called and the program will terminate after an error message is sent. This is better than simply making the system kernel generate an error message to end the program. (By the way, if cerr needs to dynamically allocate memory when writing error messages, what will happen ...)

When operator new cannot meet the memory allocation request, the new-handler function is called not only once, but repeats until enough memory is found. The code for implementing repeated calls can be seen in Clause 8. Here I use descriptive language to explain: a well-designed new-handler function must implement one of the following functions.
· Generate more available memory. This may make operator new's next attempt to allocate memory possible. One way to implement this policy is to allocate a large memory block when the program starts and release it when new-handler is called for the first time. The release is accompanied by some user warning information, such as a small amount of memory, the next request may fail unless there is more available space.
· Install another new-handler function. If the current new-handler function cannot generate 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 latest one will be used when operator new calls New-handler. (Another work und of this policy is to allow new-handler to change its own running behavior, so it will do different things in the next call. The method is to enable new-handler to modify static or global data that affects its own behavior .)
· Remove new-handler. That is, pass a null pointer to set_new_handler. If new-handler is not installed and operator new fails to allocate memory, a standard STD: bad_alloc type exception is thrown.
· Throw an STD: bad_alloc or an exception of other types inherited from STD: bad_alloc. Such exceptions will not be caught by operator new, so they will be sent to the place where the initial memory request was made. (Throwing different types of exceptions violates operator new exception specifications. The default behavior in the specification is to call abort, so when new-handler throws an exception, be sure that it is inherited from STD: bad_alloc. For more information about exception specifications, see the m14 clause .)
· No response is returned. A typical method is to call abort or exit. Abort/exit can be found in the Standard C library (and the Standard C ++ library, refer to clause 49 ).

The above selection gives you great flexibility to implement the new-handler function.

The method used to handle memory allocation failures depends on the class of the object to be allocated:

Class X {public: static void outofmemory ();...}; class y {public: static void outofmemory ();...}; x * P1 = new x; // if the allocation is successful, call X: outofmemoryy * P2 = New Y; // if the allocation is unsuccessful, call Y: outofmemory.

C ++ does not support the new-handler function for classes, and does not need it. You can implement it by yourself, as long as you provide your own version of set_new_handler and operator new in each class. The set_new_handler of the class can specify new-handler for the class (just as the standard set_new_handler specifies the Global New-handler ). Class operator new ensures that the new-handler of the class replaces the Global New-handler when the class object is allocated memory.

Assume that the processing class X memory allocation fails. When operator new fails to allocate memory to objects of Type X, an error handler must be called each time. Therefore, a new_handler static member must be declared in the class. Then 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;};

Static members of a class must be defined outside the class. Because the default initialization value of the static object is 0, Initialization is not performed when X: currenthandler is defined.

 

New_handler X: currenthandler; // The default value of currenthandler is 0 (null)

The set_new_handler function in Class X saves any pointer passed to it and returns any pointer saved before calling it. This is exactly what set_new_handler of the standard version does:

 

new_handler x::set_new_handler(new_handler p){new_handler oldhandler = currenthandler;currenthandler = p;return oldhandler;}

Finally, let's take a look at what operator new of X has done:
1. Call the standard set_new_handler function. The input parameter is an error handler of X. This makes the new-handler function of x a Global New-handler function. Note that the following code uses the ":" symbol to explicitly reference the STD Space (the standard set_new_handler function exists in the STD space ).

2. Call global operator new to allocate memory. If the first allocation fails, global operator new will call the new-handler of X because it has just been installed as a global new-handler (see Figure 1. If the global operator new fails to be allocated to the memory, it throws the STD: bad_alloc exception, and the operator new of X captures it. Operator new of X, then restores the originally replaced Global New-handler function, and finally returns an exception.

3. If the global operator new is successfully allocated memory for objects of Type X, operator new of X will call the standard set_new_handler again to restore the original global error handler function. Finally, the pointer to the allocated memory is returned.
C ++ does this:

 

Void * X: Operator new (size_t size) {new_handler globalhandler = // install new_handler of X
STD: set_new_handler (currenthandler); void * memory; try {// try to allocate memory =: Operator new (size );}
Catch (STD: bad_alloc &) {// restore the old new_handlerstd: set_new_handler (globalhandler); throw; // throw an exception} STD: set_new_handler (globalhandler ); // restore the old new_handlerreturn memory ;}

 

If you are not pleasing to the eye if you repeatedly call STD: set_new_handler above, refer to the M9 clause to remove them.

The memory allocation processing function of Class X is generally as follows:

 

Void nomorememory (); // declare the new_handler function called when the object of X fails to allocate memory X: set_new_handler (nomorememory ); // set nomorememory to the // New-handling function of X

 

X * px1 = new x; // if the memory allocation fails, // call nomorememorystring * PS = new string; // if the memory allocation fails, call the Global New-handling function X :: set_new_handler (0); // set the new-handling function of X to Null x * px2 = new x; // If memory allocation fails, throw an exception immediately // (Class X does not have the new-handling function)

You will notice that the implementation code is the same if you do not consider classes in the above cases, and it is natural that you can reuse them elsewhere. As stated in cla41, inheritance and templates can be used to design reusable code. Here, we combine the two methods to meet your requirements.

You only need to create a base class of "Mixin-style, this base class allows the subclass to inherit from a specific function. This refers to the function of creating a new-handler class. A base class is designed to allow all sub-classes to inherit the set_new_handler and operator new functions. The design template is to make each sub-class have different currenthandler data members. This sounds complicated, but you will see that the code is actually quite familiar. The difference is that it can be reused by any class now.

 

Template <class T> // provides the class newhandlersupport supported by set_new_handler {// The base class public: static new_handler set_new_handler (new_handler P) of the mixed style ); static void * operator new (size_t size); Private: static new_handler currenthandler;}; Template <class T> new_handler newhandlersupport <t >:: set_new_handler (new_handler P) {new_handler oldhandler = currenthandler; handler = P; return oldhandler;} template <class T> void * newhandlersupport <t >:: operator new (size_t size) {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); Return memory;} // This sets each currenthandler to 0 template <class T> new_handler newhandlersupport <t >:: currenthandler;
With this template class, it is easy to add the set_new_handler function to Class X: As long as X inherits from newhandlersupport <x>: // note inheritance from Mixin base class template. (See // my article on counting objects for information on why // Private inheritance might be preferable here .) class X: Public newhandlersupport <x> {... // as before, but no declarations for}; // set_new_handler or operator new

When using X, you still don't have to worry about what it is doing behind the scenes; the old code still works. This is good! Those things you often ignore are often the most trustworthy.

Using set_new_handler is a convenient and easy way to handle insufficient memory. This is much better than packing every new in the try module. Moreover, templates such as newhandlersupport make it easier to add a specific new-handler to any class. The inheritance of "mixed style" inevitably introduces the topic to multi-inheritance. Before turning to this topic, you must read Article 43 first.

Before December 31, 1993, C ++ always asked operator new to return 0 when the memory allocation fails. Now it is required that operator new throw the STD: bad_alloc exception. Many C ++ programs are written before the compiler starts to support new specifications. The C ++ Standards Committee does not want to discard the code that already complies with the return 0 specification, so they provide an additional form of operator new (and operator new []-See article 8) to continue providing the return 0 function. These forms are called "No throws" because they have never used a throw, but use a nothrow object at the entry point using new:

 

Class widget {...}; widget * pw1 = new widget; // The allocation fails to throw STD: bad_alloc ifif (pw1 = 0 )... // This check must fail. widget * pw2 = new (nothrow) widget; // if the allocation fails, return 0if (pw2 = 0 )... // This check may be successful

Whether it is in the "regular" (that is, throwing an exception) form of new or "no throw" form of new, it is important that you have to prepare for memory allocation failure. The simplest method is to use set_new_handler, because it is useful for both forms.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.