In-depth C + + new

Source: Internet
Author: User

Transferred from: http://blog.csdn.net/songthin/article/details/1703966

New "is a key word for C + + and is also an operator. There's a lot of talk about new, because it's really complicated and mysterious, and I'll summarize what I've learned about new.

The process of new when we use the keyword new to dynamically create an object on the heap, it actually does three things: get a piece of memory space, call the constructor, and return the correct pointer. Of course, if we create a variable of a simple type, then the second step is omitted. If we define one of the following classes A:class a {   int i; public:    A (int _i): I (_i*_i) {}    void Say ()  {PR intf ("i=%d/n", I); } }; Call new:a* PA = new A (3); then the process of creating an object dynamically is roughly equivalent to the following three sentences (just roughly): A * pa = (A *) malloc (sizeof (A)); PA->A::A (3); return PA; Although in effect, these three sentences also get a valid pointer to the A object on the heap PA, but the difference is that when malloc fails, it does not call the allocation memory failure handler New_handler, and using new will. So we have to use new as much as we can, unless we have some special needs. The three forms of new have so far been referred to as "new operator" or "new expression", but in fact a reference to new in C + + may at least represent the following three meanings: new operator, operator New, placement new. New operator is the new that we normally use, and its behavior is the three steps we said earlier, and we can't change it. But it is possible for us to change the behavior in a particular step if it does not meet our specific requirements. The last step in three steps is simply to do a pointer type conversion, nothing to say, and in the compiled code also does not need this conversion, just artificial understanding. But there is something in the first two steps. The first step in new operator allocates memory is actually done by calling operator new, where new is actually a subtraction-like operator and therefore can be overloaded. operator new By default first calls the code that allocates memory, tries to get space on a heap, returns if successful, and if it fails, goes to call a new_hander, and then continues to repeat the previous procedure. If we are not satisfied with this process, we can overload operator new to set the behavior we want. Example: Class A {public:    void* operator new (size_t size)    {       printf ("operator new called/n"); &nb sp;      return:: operator new (size);   };
A * a = new A (); here by:: operator new invokes the original global new, implementing a word output before allocating memory.    The global operator new can also be overloaded, but it is no longer possible to use new recursively to allocate memory, but only malloc: void* operator new (size_t size) {printf ("Global new/n"); return malloc (size); As appropriate, delete also has delete operator and operator delete, which can also be overloaded. And, if operator new is overloaded, it should also be overloaded operator delete, which is a good programming habit. New's third form,--placement new, is used to implement the positioning construct, so you can implement the second step in the new operator three-step operation, which is to construct an object on this block of memory after you have made a piece of memory that can hold the object of the specified type, which is somewhat similar to the one in the preceding code. "P->a::a (3);" This sentence, but this is not a standard notation, the correct way to use placement NEW: #include <new.h>
void Main () {char s[sizeof (A)];    A * p = (A *) s; New (P) A (3);    P-&GT;A::A (3); P->say (); The reference to the header file <new> or <new.h> is required so that placement new can be used. This strange notation for "new (p) A (3)" is placement new, which implements the ability to construct an object with a constructor of the specified type at the specified memory address, followed by a (3) as an explicit call to the constructor. It is not difficult to find that the specified address can be either a stack or a heap, placement does not differentiate this. However, unless it is particularly necessary, do not use placement new directly, this is not used to construct the formal wording of the object, but is a step of the new operator. Using the new operator compiler automatically generates the code for the call to placement new, so the code that calls the destructor with delete is also generated accordingly. If you are using placement new on the stack as above, you must call the destructor manually, which is also the only case where the destructor is explicitly called: P->~a (); When we think that the default new operator management of memory does not meet our needs, Placement new is useful when you want to manage your memory manually. Allocator in STL uses this approach, with placement new for more flexible and efficient memory management. Handling memory allocation exceptions as previously stated, the default behavior of operator new is to request allocation of memory, return this memory address if successful, or call a new_handler if it fails, and then repeat the process. Therefore, to return from the execution of operator new, one of the following conditions must be met: lAllocated Memory succeededLBad_alloc exception thrown in New_handlerLcall exit () or a similar function in New_handler to end the programThus, we can assume that by default operator new behaves like this: void* operator new (size_t size) {   void* p = null    while (!) p = malloc (size))    {       if (null = = New_handler)            throw Bad_alloc ();        try        {           New_handler ();       }        catch (Bad_alloc e)         {          throw e;        }        catch (...)        {}   }  & nbsp return p; By default, the New_handler behavior is to throw a Bad_alloc exception, so the loop will only execute once. However, if we do not want to use the default behavior, you can customize a new_handler and use the Std::set_new_handler function to make it effective. In the custom new_handler, we can throw an exception, we can end the program, or we can run some code to make it possible for the memory to be free, so that the next assignment may succeed or through Set_new_hanDler to install another potentially more efficient new_handler. For example: void Mynewhandler () {   printf ("New handler called!/n");    throw Std::bad_alloc ();}
Std::set_new_handler (Mynewhandler); Here the New_handler program outputs a word before throwing an exception. It should be noted that in New_handler's code it should be noted that there is no further nesting of calls to new, because if this call to new fails again, it may result in a call to New_handler, resulting in an infinite recursive invocation. --That's what I guess, and I haven't tried it. In programming we should note that the call to new is likely to have an exception thrown, so you should be careful to keep it transactional around new code, that is, you cannot cause incorrect program logic or data structures to occur because of a failure to throw an exception by calling new. Example: Class SomeClass {   static int count;    SomeClass () {} public:    static someclass* Get Newinstance ()    {       count++;        return new SomeClass ();   }; Static variable count is used to record the number of instances generated by this type, and in the above code, if an exception is thrown due to the failure of new allocation memory, the number of instances is not incremented, but the value of the count variable is already one more, and the data structure is corrupted. The correct wording is: static someclass* getnewinstance () {   someclass* p = new SomeClass ();    count++;  &nbsp ; return p; This way, if new fails, the exception is thrown directly, and the value of count does not increase. Similarly, it is important to note similar issues when dealing with thread synchronization: void SomeFunc () {   lock (Somemutex);//plus a lock    delete p;    p = new SomeClass ();    unlock (Somemutex); At this point, if new fails, unlock will not be executed, resulting in a pointer to an incorrect addressThe presence of P will also cause Somemutex to never be unlocked. This is a situation that should be avoided. (see: C + + Proverbs: Code for exception security) STL's memory allocation and traits techniques the behavior of the SGI STL's memory allocator is analyzed in detail in the STL code anatomy. Unlike the direct use of new operator, SGI STL does not rely on the default memory allocation method of C + +, but instead uses a set of self-implemented scenarios. First, the SGI STL will allocate the available memory block to become the memory available to the current process, and when the program does need to allocate memory, try to get the memory from those large chunks of memory that have been requested, and if it fails, try allocating large memory for the whole block. This approach effectively avoids the occurrence of large amounts of memory fragmentation and improves memory management efficiency. To achieve this, STL uses placement new to construct objects by using placement new on its own managed memory space to achieve the functionality of the original new operator. Template <class T1, class t2>, inline void construct (t1* p, const t2& value) {   new (p) T1 (value);} This function receives a constructed object, constructs a new object on the given memory address p by copying the construct, and the code in the second half of T1 (value) is the placement new syntax to call the constructor, if the incoming object value is the required type T1, Then this is equivalent to invoking the copy constructor. Similarly, because of the use of placement new, the compiler does not automatically generate code to call destructors, which requires manual implementation: template <class t> inline void destory (t* pointer) {    Pointer->~t (); At the same time, there is an destory version of the STL that receives two iterators that can destroy objects in a specified range on a container. A typical implementation is to call a destructor one at a span through a loop for objects within that range. It is necessary to do this if the object being passed in is a non-simple type, but if you pass in a simple type, or you do not have to call the destructor's custom type (for example, a struct that contains only a few int members), then calling the destructor one by one is not necessary, and time is wasted. To do this, the STL uses a technique called "type Traits", in which the compiler determines whether the type being passed in needs to call the destructor: TemplAte <class forwarditerator> inline void Destory (forwarditerator first, ForwardIterator last) {   __desto Ry (First, Last, Value_type (first)); where Value_type () is used to remove the type information of the object pointed to by the iterator, then: Template<class ForwardIterator, class t> inline void __destory ( ForwardIterator first, ForwardIterator last, t*) {   typedef typename __TYPE_TRAITS&LT;T&GT;::HAS_TRIVIAL_ destructor Trivial_destructor;    __destory_aux (First, Last, Trivial_destructor ()); }//If you need to call the destructor: Template<class forwarditerator> inline void __destory_aux (forwarditerator first, ForwardIterator Last, __false_type) {   for (; first < last; ++first)        destory (&* First); Because first is an iterator, *first takes out its real content and then uses & to fetch the address}//If not needed, do nothing: tempalte<class forwarditerator> inline void __destory _aux (ForwardIterator First, ForwardIterator last, __true_type) {} because the above functions are all inline, multi-layered function calls do not affect performance. The result of the final compilation is a For loop or nothing, depending on the type. The key here is to __type_traits<t> this.On a template class, it defines the results of different has_trivial_destructor based on different t types, and if T is a simple type, it is defined as the __true_type type, otherwise it is defined as the __false_type type. Where __true_type, __false_type is nothing more than two classes without any content, there is no point in the execution of the program, but in the compiler's view it has very important implications for how the template is specific, as shown in the code above. __type_traits<t> is also a series of special template classes: struct __true_type {}; struct __false_type {}; Template <class t> struct __type_traits {public:    typedef __false _TYPE has_trivial_destructor;  & nbsp ...... }; template<>//template-specific struct __type_traits<int>   //int special version {public:    typedef __ True_type Has_trivial_destructor;    ...}; ...//Other simple types of special versions if you want to define a custom type MyClass to not call destructors, you only need to define a specific version of __type_traits<t> to:template<> struct __ type_traits<myclass> {public:    typedef __true_type has_trivial_destructor;    ...}; Template is a more advanced C + + programming skills, template specificity, template bias is a very tricky thing, STL type_traits full use of template-specific features, the implementation of the compiler in the program at the time of the compiler to decide for each call to use which special version, So it greatly improves the running efficiency of the program without increasing the complexity of programming. More detailed content can be referred to the STL source code analysis of the relevant content in the second to third chapter. New and delete with "[]" We often create an array dynamically with new, for example: Char* s = new char[100]; ... delete s; strictly speaking, the above code is not correct, because we are allocating memory using new[] instead of simple new, but releasing memory is using Delete. The correct notation is to use delete[]:delete[] s; however, the code for the above error seems to compile and execute without error. In fact, new and new[], delete and delete[] are different, especially when used to manipulate complex types. If you are using new[]:myclass* p = new MYCLASS[10] For one of our custom class MyClass, the result of this code is that 10 consecutive MyClass instances are allocated on the heap, and the constructors have been called sequentially. So we got 10 available objects, which is different from Java, C #, so the result in Java, C # just got 10 null. In other words, when using this notation, MyClass must have a constructor with no arguments, or a compile-time error will be found because the compiler cannot invoke the constructor with parameters. When such a construct succeeds, we can release it again and use delete[]:delete[] p when we release it, and when we call delete[] on a dynamically allocated array, its behavior varies depending on the type of variable being applied. If p points to a simple type, such as int, char, and so on, the result is just that the memory is recycled, and using delete[] is no different from delete, but if p points to a complex type, delete[] calls the destructor for each object that is dynamically allocated, and then frees the memory. Therefore, if we use delete directly to recycle the P pointers given above, although there is no error in the compile period (because the compiler does not see how the pointer p is allocated at all), a debug assertion failed hint is given at runtime (in the case of debug). Here, it's easy to ask a question--delete[] how do you know how many objects to call destructors? To answer this question, we can first look at the overloads of New[]. Class MyClass {   int A; public:    MyClass () {printf ("ctor/n");}    ~myclass () {printf ( "dtor/n"); } };
void* operator new[] (size_t size) {void* p = operator new (size);    printf ("Calling new[] with size=%d address=%p/n", size, p); return p; }
Main function Myclass* MC = new MYCLASS[3]; printf ("Address of mc=%p/n", MC); Delete[] MC; Run this code to get the result: (VC2005) calling new[] with size= -address= 003a5a58Ctorctorctoraddress of mc= 003a5a5cDtordtordtor Although the call results for constructors and destructors are expected, there is a problem with the size of the requested memory space and the value of the address. The size of our class MyClass is obviously 4 bytes, and there are 3 elements in the requested array, so we should apply a total of 12 bytes, but in fact the system has applied for us 16 bytes, and in operator new[] The memory address we obtained after returning is the result of the actual application of the memory address value plus 4. That is, when an array is dynamically allocated for a complex type, the system automatically allocates 4 bytes in front of the resulting memory address, and we have reason to believe that the 4-byte content is related to the length of the dynamically allocated array. With single-step tracking, it is easy to see that the 4 bytes correspond to an int value of 0x00000003, which means that the number of objects we have allocated is recorded. Changing the number of allocations and then observing the results again confirms my idea. So, we also have reason to think that new[] operator behavior is equivalent to the following pseudo code: template <class t> t* new[] (int count) {   int size = sizeof (T) * C Ount + 4;    void* p = t::operator new[] (size);    * (int*) p = count;    t* pt = (t*) ((int) p + 4);    for (int i = 0; i < count; i++)        new (&pt[i]) T ();    return pt; The schematic code above omits the exception handling part, just shows what its real behavior is when we use new[] to dynamically allocate an array to a complex type, from which we can see that it allocates 4 bytes more memory than expected and uses it to save the number of objects. Then using placement new for each of the subsequent spaces calls the parameterless constructor, which explains why the class must have a parameterless constructor and finally return the first address. Similarly, we can easily write the corresponding delete[] implementation code: template <class t> void delete[] (t* PT) {   int count = ((int*) PT) [-1]; &nbsp ;  for (int i = 0; i < count; i++)        pt[i].~t ();    void* p = (void*) ((int) pt–4);    t::operator delete[] (p); This shows that by default operator new[] behaves the same as operator new, operator delete[] and operator delete are also different, unlike the new operator and new[] operator, Delete operator and delete[] operator. Of course, we can choose to overload operator new and delete with and without "[]" for different needs to meet different specific needs. Modify the code of the previous class MyClass-comment out the destructor, and then look at the output of the program: calling new[] with size=12 address=003a5a58ctorctorctoraddress of mc= 003a5a58 this time, new[] Honestly applied for 12 bytes of memory, and the results of the application and new[] operator return the result is the same, it seems, whether to add 4 bytes before, only depends on the class has no destructor, of course, so that is not accurate, The correct argument is whether the class needs to call the constructor, because there are two cases in which the class does not declare the destructor, but it also applies 4 more bytes: First, the class has a member that needs to call the destructor, and the second is the class that inherits from the call to the destructor. Thus, we can recursively define "classes that need to call destructors" for one of the following three scenarios: 1 explicitly declares that the destructor of 2 has a member of the class that needs to call the destructor 3 inherits from the class that needs to call the destructor, and when the dynamic request array of a simple type, it does not request more than 4 bytes. So in both cases, the release of memory using delete or delete[] can be, but in order to develop good habits, we should also note that as long as the dynamic allocation of the array, the release of the use of delete[]. How to know the length when releasing memory but this also brings up new problems, since the application does not have to call the destructor of the class or simple type array without logging the number of information, then operator delete, or more directly say free () is how to reclaim this memory? This will examine the structure of the memory returned by malloc (). with nEw[] Similar to the fact that there is actually more than one byte of content applied to malloc () when requesting memory, except that it has nothing to do with the type of the variable being applied, and we can understand this from the parameters passed in when we call malloc-it only receives the length of memory to be requested, It doesn't matter what type this memory is used to hold. Run the following code to do an experiment: char *p = 0; for (int i = 0; i <; i + = 4) {   char* s = new char[i];    printf ("alloc%2d bytes, address=%p d istance=%d/n ", I, S, s-p);    p = s; We directly see VC2005 release version of the running results, debug version contains more debugging information, here is not analyzed: alloc 0 bytes, address=003a36f0 distance=3815152alloc  4 bytes, address=003a3700 distance=16alloc 8 bytes, address=003a3710 distance=16alloc, bytes 003a3720 distance=16alloc bytes, address=003a3738 distance=24alloc bytes, address=003a84c0 distance=19848alloc b Ytes, address=003a84e0 distance=32alloc bytes, address=003a8500 distance=32alloc, Bytes address=003a8528 40alloc bytes, address=003a8550 distance=40 each time the number of bytes allocated is recorded with the difference from the last assignment, the first difference has no practical meaning, and there is a large difference between the two. It may be that the memory has been allocated, so it is ignored. The smallest difference in the result is 16 bytes, until we apply 16 bytes, the difference becomes 24, followed by a similar pattern, then we can assume that the memory structure of the application is as follows:It is not difficult to see, when we are allocating a memory, the resulting memory address and the last last address is at least 8 bytes apart (in the debug version more), then we can guess, these 8 bytes should be recorded in this section of the memory related to the information. Observing the contents of these 8 sections, the results are as follows:The right side of the figure is a 16-binary representation of the contents of the 8 bytes before each assigned address, and the red line indicates that the first byte in the 8 bytes is multiplied by 8, which is the distance from the two allocations, and the second byte is the same meaning and represents the high 8 bits. That's what it says. The first two bytes in the 8 bytes of the preceding space record the length of the allocated memory, and the subsequent six bytes may be related to the information about the free memory linked list, which is used to provide the necessary information when translating memory. This answers the question raised earlier, the original C + + in allocating memory has been recorded enough information to reclaim memory, but we usually do not care about it.

In-depth C + + new

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.