In-depth C + + new

Source: Internet
Author: User
Tags traits

Reprinted 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 () {printf ("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");
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 instead of using new to allocate memory recursively, only malloc is used: 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: L Allocate memory success L New_handler throw Bad_alloc exception L New_handler call exit () or a similar function, so that the program ends so 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 (...)
{}
}
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 might succeed, or you can install another potentially more efficient new_ by Set_new_handler. Handler 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* getnewinstance ()
{
count++;
return new SomeClass ();
}
The 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 because of a failure to allocate memory for new, 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++;
return p;
This way, if new fails, the exception is thrown directly, and the value of count does not increase. Similarly, when dealing with thread synchronization, you should also be aware of similar issues: void SomeFunc ()
{
Lock (Somemutex); Add a lock
Delete p;
p = new SomeClass ();
Unlock (Somemutex);
At this point, if new fails, unlock will not be executed, causing not only the presence of a pointer to an incorrect address, but also the Somemutex will 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 of the constructor called in the new syntax, 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 that calls destructors, which need to be implemented manually: 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)
{
__destory (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<t>::has_trivial_destructor Trivial_destructor;
__destory_aux (First, Last, Trivial_destructor ());
}
If you need to call a 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
}
Do nothing if you don't need it:
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, and the result of the compilation is a for-loop or nothing, depending on the type. The key here is to __type_traits<t> this template class, which defines the results of different has_trivial_destructor based on the different T types, 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;
......
};
template<>//Template specificity
A special version of struct __type_traits<int>//int
{
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 a destructor, 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;
......
}; templates are 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[], rather than simple new, but freeing memory with 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) * count + 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];
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 (). Similar to new[], in fact, the application of the memory in malloc () is also more than a few bytes of content, but this is not related to the type of the variable requested, we can also understand this from the parameters passed in when malloc is called-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 distance=%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 minimum difference in the result is 16 bytes, and 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 want to allocate a memory, The resulting memory address and the last last address are at least 8 bytes apart (more in the debug version), so we can guess that the 8 bytes should record information about the memory allocated to this section. Observing the contents of these 8 sections, the results are as follows: the right side of the figure is the 16-binary representation of the 8-byte content before each assigned address, and the red line indicates that the first byte in the 8 bytes is multiplied by 8 to get the distance from the two allocations, and the larger length of the experiment is allocated at once. The second byte is also the meaning, and represents the high 8 bits, also said that the front empty 8 bytes of the first two bytes recorded the length of the allocated memory information, the following six bytes may be related to the free memory linked list information, in the translation of memory to provide the necessary information. This is the answer to the previous question, the original C + + in allocating memory has been recorded enough information for the recovery of memory, but we don't usually 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.