Deep-new__c++ of C + +

Source: Internet
Author: User
Tags int size traits

"New" is a keyword in 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 new process when we use the keyword new to create an object dynamically on the heap, it actually does three things: get a chunk of memory, call the constructor, and return the correct pointer. Of course, if we create a simple type of variable, the second step is omitted. If we define the following class 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); So the process of creating an object is roughly equivalent to the following three sentences (in general): A * pa = (*) malloc (sizeof (A));
PA->A::A (3);
Return PA; In effect, these three words also get a valid pointer to the A object on the heap, but the difference is that when malloc fails, it does not invoke the allocation of memory failure handler New_handler, which is the case with new. So we still have to use new as much as possible, unless there are some special requirements. New three forms so far, the new one referred to in this article refers to "new operator" or "new expression", but in fact in C + + a reference to new, at least, may represent the following three kinds of meanings: new operator, operator New, placement new. The new operator is the new one we usually use, and its behavior is the three steps mentioned above, we can't change it. But the behavior in a particular step, if it does not meet our specific requirements, we are likely to change it. The last step in the three step is simply to make a pointer type conversion, there is nothing to say, and in the compiled code does not need this conversion, but artificial understanding. But the first two steps have some content. The first step in allocating memory for new operator is actually done by calling operator new, where new is actually an operator like subtraction and therefore can be overloaded. operator new By default, the code that allocates memory is invoked first, attempts to get space on a heap, returns if successful, and if it fails, then calls a New_hander, and then repeats the previous procedure. If we are not satisfied with this process, we can overload operator new to set the behavior we want. For example: Class A
{
Public
void* operator new (size_t size)
{
printf ("operator new called/n");
Return:: operator new (size);
}
};

A * a = new A (); This is done by:: operator new invokes the original global new, realizing the output of a sentence before allocating memory. The global operator new can also be overloaded, but so it is not possible to recursively use new to allocate memory, only to use malloc: void* operator new (size_t size)
{
printf ("Global new/n");
return malloc (size);
Accordingly, delete also has the delete operator and operator delete, which can also be overloaded. And, if operator new is overloaded, it should also be appropriate to overload the operator delete, which is good programming practice. The third form of new--placement new is used to implement positioning constructs, so you can implement the second step in the new operator three-step operation, which is to construct an object on this memory after you have acquired a piece of memory that can hold the object of the specified type, which is somewhat similar to the one in the previous code "P->a::a (3);" This sentence, but this is not a standard wording, 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 you can use placement new. Here "New (p) A (3)" is placement new, which implements the function of constructing an object with a specified type of constructor on the specified memory address, followed by a (3) as an explicit call to the constructor. It is not hard to find that this specified address can be either a stack or a heap, placement. However, unless it is particularly necessary not to use placement new directly, this is not a formal notation for constructing objects, but a step in the new operator. Using the new operator compiler automatically generates the code for the call to placement new, and therefore generates the code that invokes the destructor when the delete is used. If you use placement new on the stack like the one above, you must call the destructor manually, which is the only case of an explicit call to a destructor: P->~a (); Placement new is useful when we feel that the default new operator management of memory does not meet our needs, and that we want to manage our memory manually. Allocator in STL uses this approach, with placement new to enable more flexible and efficient memory management. Handling memory allocation exceptions as mentioned earlier, the default behavior of operator new is to request allocating memory, return this memory address if successful, and call a new_handler if it fails, and then repeat the process. Thus, to return from the execution of operator new, one of the following conditions must be met: L allocating memory success L New_handler throw Bad_alloc exception l New_handler call Exi T () 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 behavior of New_handler is to throw a Bad_alloc exception, so the loops above are executed only once. But if we don't want to use the default behavior, we can customize a new_handler and use the Std::set_new_handler function to make it effective. In a custom new_handler, we can throw exceptions, end the program, or run some code to make it possible for the memory to be idle, so that the next allocation may be successful, or it can be 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 prints a sentence before throwing an exception. It should be noted that in New_handler's code you should be careful to avoid nesting calls to new, because if you call new here again, it may cause a call to the new_handler, resulting in an infinite recursive call. --That's what I guess, and never tried. In programming, we should notice that the call to new is likely to have an exception thrown, so you should pay attention to maintaining its transactional around the new code, that is, you cannot cause incorrect program logic or data structure to occur because an exception is thrown by invoking new failure. For 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, in which the number of instances is not increased, but the value of the count variable is already added, and the data structure is corrupted if the exception is thrown because the new memory fails. The correct formulation is: Static someclass* getnewinstance ()
{
someclass* p = new SomeClass ();
count++;
return p;
This way, if new fails to throw an exception directly, the value of count does not increase. Similarly, when handling thread synchronization, you should also pay attention to similar problems: 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 p to an incorrect address, but also the Somemutex will never be unlocked. This is a situation that should be avoided. (Reference: C + + Proverbs: For exception-safe code) STL memory allocation and traits techniques in the "Analysis of STL source code" in detail the SGI STL memory allocator behavior. Unlike the direct use of new operator, the SGI STL does not rely on C + + default memory allocation, but instead uses a set of self implemented scenarios. First of all SGI STL will be available to allocate the entire block of memory to become the current process of memory available, when the program does need to allocate memory, the first of these large chunks of memory to try to obtain memory, if the failure of the whole block to allocate large memory. This approach effectively avoids the emergence of a large amount of memory fragmentation and improves the efficiency of memory management. To achieve this, the STL uses placement new to construct the object 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 a given memory address p by copying it, and the second half T1 (value) in the code is the method of calling the constructor in the placement new syntax, if the Passed-in object value is the type T1 that is required, So here's the equivalent of calling the copy constructor. Similarly, because of the use of placement new, the compiler does not automatically generate code that invokes the destructor, which requires manual implementation: template <class t>
inline void Destory (t* pointer)
{
Pointer->~t ();
At the same time, the STL also has a destory version that receives two iterators to destroy all objects within a specified range on a container. A typical implementation is to call a destructor on an object in this range through a loop. If the object being passed in is a non simple type, this is necessary, but if you are passing in a simple type, or if it is not necessary to call the custom type of the destructor (for example, a struct that contains only a few int members), then invoking the destructor is unnecessary and wastes time. To this end, the STL uses a technique called "type Traits", in which the compiler determines whether the incoming type needs to invoke a destructor: template <class forwarditerator>
inline void Destory (ForwardIterator, ForwardIterator last)
{
__destory (The Last, Value_type (a));
where Value_type () is used to take out the type information of the object that the iterator points to, so: Template<class ForwardIterator, Class t>
inline void __destory (ForwardIterator, ForwardIterator last, t*)
{
typedef typename __type_traits<t>::has_trivial_destructor Trivial_destructor;
__destory_aux (The Last, Trivial_destructor ());
}
If you need to call the destructor:
Template<class forwarditerator>
inline void __destory_aux (ForwardIterator, ForwardIterator last, __false_type)
{
for (; a < last; ++first)
Destory (&*first); Because the primary is an iterator, *first takes out its real content and then uses the & address
}
If you don't need it, do nothing:
Tempalte<class forwarditerator>
inline void __destory_aux (ForwardIterator, ForwardIterator last, __true_type)
{} Because the above functions are all inline, multilayer function calls do not affect performance, and the resulting compiled results are just a for loop or nothing based on the specific type. The key here is to __type_traits<t> this template class, which defines different has_trivial_destructor results based on different t types, and if T is a simple type, it is defined as __true_type type, otherwise it is defined as __false_type type. Where __true_type and __false_type are just two classes without any content, which have little meaning to the execution of the program, the compiler seems to have a very important directive on how to make the template special, 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 Special
struct __type_traits<int>//int's special edition
{
Public
typedef __true_type Has_trivial_destructor;
......
};
...//Other simple type of special version if you want to define a custom type MyClass to not invoke a destructor, you just need to define a special version of the __type_traits<t>: template<>
struct __type_traits<myclass>
{
Public
typedef __true_type Has_trivial_destructor;
......
}; Template is a relatively advanced C + + programming skills, template specificity, template-specific specificity is very strong things, the STL in the Type_traits full use of template-specific features, implemented in the program compile time through the compiler to decide for each call to use which special version, Thus, without increasing the complexity of programming, the operation efficiency of the program is greatly improved. More detailed content can refer to the "STL source Analysis" in the second to third chapter of the relevant content. New and delete with "[]" We often create an array dynamically via new, for example: char* s = new char[100];
......
Delete S; Strictly speaking, the above code is not correct, because we use new[when allocating memory, rather than simple new, but it uses delete when releasing memory. The correct formulation is to use delete[]: delete[] s; However, the code of the above error seems to compile and execute without causing any errors. In fact, new and new[], delete and delete[are different, especially when it comes to manipulating complex types. If we use new[for a class MyClass our custom: myclass* p = new MYCLASS[10]; The result of the above code is that 10 consecutive MyClass instances are allocated on the heap. And they've called the constructors in turn, so we've got 10 objects available, which are different from Java and C #, and the result in Java and C # is just 10 null. In other words, the MyClass must have a constructor with no arguments when you use this method, or you may find a compile-time error because the compiler cannot invoke a constructor with parameters. When this construction succeeds, we can release it again and use delete[when releasing it: delete[] p; When we call delete[on a dynamically allocated array, the behavior varies depending on the type of variable being requested. If p points to a simple type, such as int, char, and so on, the result is only that the memory is recycled, and the use of delete[] is not the same as delete, but if p is pointing to a complex type, delete[will call the destructor for each object that is dynamically allocated and then release the memory. Therefore, if we use delete directly to reclaim the assigned p pointer above, although the compile period does not report anything wrong (because the compiler simply does not see how the pointer p is allocated), a debug assertion failed prompt is given at run time (debug case). Here, it's easy to ask a question--delete[how to know how many objects to invoke destructors for. To answer this question, we can first look at the overload 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 section of code, the result is: (VC2005) calling new[] with size= -address=003a5a58ctor ctor ctor address of mc=003a5a5cDtor Dtor Dtor Although the results of calls to constructors and destructors are expected, there is a problem with the amount of memory space requested and the number of addresses. The size of our class MyClass is obviously 4 bytes, and there are 3 elements in the requested array, so you should apply for 12 bytes altogether, but the system actually applies 16 bytes for us, and in operator new[] The memory address we get when we return 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 empty 4 bytes before the resulting memory address, and we have reason to believe that the contents of these 4 bytes are related to the length of the dynamically allocated array. With Single-step tracking, it's easy to see that these 4 bytes correspond to an int value of 0x00000003, which means that the number of objects we allocate is recorded. Changing the number of allocations and then looking again at the results confirms my thinking. Therefore, we also have reason to think new[] operator behavior is equivalent to the following pseudocode: 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, but shows what the real behavior is when we dynamically allocate an array using new[for a complex type, from which you can see that it allocates more than 4 bytes of memory and uses it to hold the number of objects. Then, using placement new to invoke the parameterless constructor for each subsequent space, this explains why the class must have a parameterless constructor in this case, and then return the first address. Similarly, it is easy to write the implementation code for the corresponding delete[]: 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, unlike new operator and new[] operator, Delete operator and delete[] operator. Of course, we can choose operator new and delete with and without "[]" to meet different specific needs, depending on the needs. Modify the code in the previous class MyClass--comment out the destructor, and then look at the output of the program: calling new[] with size=12 address=003a5a58 ctor ctor ctor address of Mc=003a5a5 8 this time, new[] Honestly requested 12 bytes of memory, and the results of the application and new[] operator returned the same result, it seems, whether to add 4 bytes before, only depends on the class has no destructor, of course, it is not accurate, The correct argument is whether this class needs to call the constructor, because of the following two cases, although this class does not declare destructors, it applies more than 4 bytes: One is that the class has a member that needs to invoke the destructor, and the second is that the class inherits from the class that calls the destructor. As a result, we can recursively define "classes that need to invoke destructors" for one of the following three scenarios: 1 explicitly declares that 2 of the destructor's 3 inherits the members of the class that need to invoke the destructor, similar to the class that calls the destructor, the dynamic request for a simple type of array is not more than 4 bytes. So in both cases, it's OK to use delete or delete[when you release the memory, but in order to develop good habits, we should be aware that as long as the dynamically allocated array is released, use delete[]. How to know the length when freeing memory but this brings up a new problem, since the application does not need to call a destructor's class or a simple type of array without recording the number of information, then operator delete, or more directly, free () is how to reclaim this block of memory. This is to study the structure of the memory returned by malloc (). Similar to the new[], in fact, when malloc () applies for memory, it also requests several bytes of content, except that it has nothing to do with the type of the variable being requested, and we can understand this from the arguments passed in when we call malloc-it only receives the length of the memory to be requested, It does not matter what type of memory is used to save the block. 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 look directly at the VC2005 release version of the operation results, the debug version contains more debugging information, here is not analyzed: alloc 0 bytes, address=003a36f0 distance=3815152 alloc  4 bytes, address=003a3700 distance=16 alloc 8 bytes, address=003a3710 distance=16 alloc, bytes 3720 distance=16 alloc bytes, address=003a3738 distance=24 alloc-Bytes, address=003a84c0 distance=19848 alloc-byt ES, address=003a84e0 distance=32 alloc bytes, address=003a8500 distance=32 alloc-bytes, address=003a8528 distance=40 Alloc bytes, address=003a8550 distance=40 each time the number of bytes allocated is higher than the previous one, the difference between the previous assignment is recorded, the first difference is not practical, and there is a large difference in the middle. It is possible that this memory has been allocated and ignored. The smallest difference in the result is 16 bytes, until we apply for 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 hard to see from the diagram that when we allocate a memory, The resulting memory address and last tail address are at least 8 bytes apart (more in the debug version), so we can assume that the 8 bytes should record information about the memory allocated to this segment. Looking at the contents of these 8 sections, the results are as follows: the right side of the figure is represented by the 16 binary of the 8 bytes of content before each assigned address, as you can see from the red line in the diagram, the first byte in these 8 bytes is multiplied by 8 to get the distance from the two-time distribution, and it is possible to assign a larger length by trial. The second byte is also the meaning, and represents the height of 8 bits, the first two bytes of these 8 bytes in front of them record the length of the allocated memory, and the next six bytes may be related to the information of the free memory list, which is used to provide the necessary information when translating memory. This answers the question raised earlier, the original C + + in the allocationThere is enough information to recycle memory, but we usually don't care about it.

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.