"new" is a keyword in C++ and also an operator. There is a lot of talk about new, because it's really complicated and mysterious. Here's a summary of what I learned about new.
New process
When we dynamically create an object on the heap using the keyword new, it actually does three things: acquires a memory space, calls a constructor, and returns the correct pointer. Of course, if we create a simple type of variable, then the second step will be 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); }};//Invoking new:A * pa = new A(3);
So the above process of dynamically creating an object is roughly equivalent to the following three sentences (just roughly):
A* pa = (A*)malloc(sizeof(A));pa->A::A(3);return pa;
Although from an effect point of view, these three sentences also get a valid pointer to the A object on the heap, but the difference is that when malloc fails, it will not call the allocation of memory failure handler new_handler, and use new words Will do. Therefore, we still want to use new as much as possible, unless there are some special requirements.
The three forms of new
So far, the new mentioned in this article refers to the "new operator" or "new expression", but in fact, in the C + + mentioned new, at least may represent the following three meanings: new operator, operator new , placement new.
The new operator is what we normally use new. Its behavior is the three steps mentioned above. We cannot change it. But specific to the behavior in a step, if it does not meet our specific requirements, we are likely to change it. The last step of the three steps is simply to do a pointer type conversion, nothing to say, and in the compiled code does not require this conversion, but human recognition only. But the first two steps have some content.
Allocating memory in the first step of the new operator is actually done by calling operator new, where new is actually the same operator as addition, subtraction, multiplication, and division, and therefore can be overloaded. The operator new by default calls the code that allocates memory, tries to get some space on the heap, returns if successful, and if it fails, then calls a new_hander and then repeats the previous process. If we are not satisfied with this process, we can overload operator new to set our desired behavior. E.g:
Class A{public: void* operator new(size_t size) { printf("operator new called/n"); return ::operator new(size); }};A* a = new A();
Here: ::operator new called the original global new, to achieve a word before the allocation of memory. The global operator new can also be overloaded, but this can no longer be used recursively to allocate memory using new, but only malloc:
Void* operator new(size_t size){ printf("global new/n"); return malloc(size);}
Correspondingly, delete also has the distinction of delete operator and operator delete, which can also be overloaded. Also, if you overload operator new, you should also overload operator delete, which is a good programming practice.
The third form of new - placement new is used to implement the positioning structure, so you can implement the second step in the new operator three-step operation, that is, after obtaining a piece of memory that can accommodate the specified type of object, in this Construct an object on memory, which is similar to the "p->A::A(3);" in the previous code, but this is not a standard way of writing. The correct way is to use placement new:
#include
Pair header files
P->~A();
When we feel that the default new operator management of memory can not meet our needs, and you want to manually manage memory, placement new is useful. Allocators in STL use this approach, with placement new to achieve more flexible and effective memory management.
Handling memory allocation exceptions
As mentioned earlier, the default behavior of operator new is to request memory allocation. If successful, this memory address is returned. If it fails, a new_handler is called and the process is repeated. Therefore, if you want to return from the execution of operator new, you must satisfy one of the following conditions:
l allocate memory successfully
l new_handler throw bad_alloc exception
l new_handler call exit () or similar function to end the program
So, we can assume that the behavior of operator new by default is 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 above loop will only be executed once. But if we don't want to use the default behavior, we can customize a new_handler and make it work using the std::set_new_handler function. In the custom new_handler, we can throw an exception, you can end the program, you can also run some code so that there may be free memory, so that the next allocation may be successful, you can also install another set_new_handler may be more A valid new_handler. E.g:
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 the new_handler's code, care should be taken to avoid nesting calls to new, because if the call to new fails then it may result in a call to new_handler, resulting in an infinite recursive call. - This is my guess, and I haven't tried it.
During programming, we should note that the call to new may have an exception thrown, so we should pay attention to maintain the transaction around the new code, that can not be caused by calling the new failure to throw an exception to cause incorrect program logic Or the emergence of data structures. E.g:
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 the above code, if an exception is thrown due to failure of new memory allocation, the number of instances does not increase, but the value of the count variable is more than one. , and thus the data structure is destroyed. The correct way is:
Static SomeClass* GetNewInstance(){ SomeClass* p = new SomeClass(); count++; return p;}
In this way, if new fails, an exception is thrown and the value of count does not increase. Similarly, when dealing with thread synchronization, you should also pay attention to 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, thus not only results in the existence of a pointer p pointing to an incorrect address, but also will cause someMutex to never be unlocked. This situation is to be avoided. (Reference: C++ Rumors: Code for Unusual Security)
STL memory allocation and traits skills
In the book "Anatomy of the original STL", the behavior of the SGI STL memory allocator is analyzed in detail. Unlike using the new operator directly, the SGI STL does not rely on the C++ default memory allocation but uses a self-implemented scheme. First of all, SGI STL allocates the entire block of available memory to make it the memory available to the current process. When the program actually needs to allocate memory, it first tries to get the memory from these requested large blocks, and if it fails, it tries the whole memory again. Block allocates large memory. This approach effectively avoids the appearance of large amounts of memory fragmentation and improves memory management efficiency.
To implement this approach, STL uses placement new to construct objects by using placement new on the memory space they manage to achieve the functionality of the original new operator.
Template
This function receives a constructed object and constructs a new object at a given memory address p by copying the constructor. The last half of the code is the call to the constructor in the placement new syntax. The value of the object is exactly the type T1 required, so here is equivalent to calling the copy constructor. Similarly, due to the use of placement new, the compiler does not automatically generate code that calls the destructor, which requires manual implementation:
Template
At the same time, there is a version of destroy that receives two iterators in the STL, which can destroy all objects in a specified range on a container. A typical implementation is to call the destructor one by one through a loop to objects in this range. This is necessary if the object passed in is of a non-simple type, but if a simple type is passed in, or if it is not necessary to call a custom type of the destructor at all (for example, a structure containing only a few int members), Then it is not necessary to call the destructors one by one. It also wastes time. For this reason, STL uses a technique called "type traits", and the compiler determines if the passed type needs to call the destructor:
Template
Where value_type() is used to retrieve the type information of the object pointed to by the iterator, so:
Template
Because all of the above functions are inline, multi-level function calls do not affect performance. The final compilation result is just a for loop or nothing depending on the type. The key here is __type_traits
Struct __true_type {};struct __false_type {};template
If you want to define a custom type MyClass to not call the destructor, only the corresponding definition __type_traits
Template<>struct __type_traits
Template is more advanced C + + programming skills, template specialization, template partial specialization is even more skillful things, type_traits in STL fully take advantage of the template specialization function, achieved through the compiler at the compile time of the program to decide Which specialized version is used for each call, thus greatly improving the operating efficiency of the program without increasing the programming complexity. More detailed content can refer to "STL source code analysis" in the second and third chapters of the relevant content.
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 incorrect, because we are using new[] when allocating memory, but not simply new, but using delete when releasing memory. The correct way to use is to use delete[]:
Delete[] s;
However, the above error code seems to compile and execute without any errors. In fact, new and new[], delete and delete[] are different, especially when used to manipulate complex types. If you use new[] for a custom class MyClass:
MyClass* p = new MyClass[10];
The result of the above code is that 10 consecutive MyClass instances are allocated on the heap, and the constructors are called one after the other. So we get 10 usable objects. This is different from Java and C#. Java, C# The result of this is just getting 10 nulls. In other words, MyClass must have constructors with no arguments when using this type of writing, otherwise a compile-time error will be found because the compiler cannot call parameterized constructors.
When this construction is successful, we can release it again, using delete[]:
Delete[] p;
When we call delete[] on a dynamically allocated array, its behavior will be different depending on the type of variable requested. If p points to a simple type, such as int, char, etc., the result is just that the memory is being reclaimed. There is no difference between using delete[] and delete, but if p points to a complex type, delete[] will be allocated dynamically. The resulting destructor is called for each object, and then freed. Therefore, if we use the delete p pointer to directly use the delete to recover, although the compiler does not report any errors (because the compiler does not see how the pointer p is allocated), but at runtime (DEBUG case) A Debug assertion failed prompt will be given.
Here, we can easily ask a question - how does delete[] know how many objects to call destructors? To answer this question, we can first take a 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 code, the result is: (VC2005)
Calling new[] with size=16 address=003A5A58
Ctor
Ctor
Ctor
Address of mc=003A5A5C
Dtor
Dtor
Dtor
Although the results of the call to the constructor and the destructor are all expected, there is a problem with the size of the memory space and the value of the address. Our class MyClass is obviously 4 bytes in size, and there are 3 elements in the applied array, then we should apply for a total of 12 bytes to get it right, but in fact the system has applied for 16 bytes for us, and in the operator The memory address that we got after new[] is the result of the actual application's memory address plus 4. That is, when the array is dynamically allocated for a complex type, the system automatically vacates 4 bytes before the resulting memory address. We have reason to believe that the contents of these 4 bytes are related to the length of the dynamically allocated array. Through single-step tracking, it is easy to find that the int of these four bytes is 0x00000003, which means that we record the number of objects we allocate. Changing the number of allocations and then observing again confirms my thoughts. Therefore, we also have reason to believe that the new[] operator's behavior is equivalent to the following pseudocode:
Template
The above illustrative code omits the part of exception handling, but it shows what the true behavior is when we use new[] to dynamically allocate an array for a complex type. It can be seen that it allocates 4 bytes more than expected. The memory uses it to store the number of objects, and then uses placement new to call the no-argument constructor for each subsequent space, which explains why the class must have no-argument constructors in this case, and finally the first address return. Similarly, we can easily write the corresponding implementation code for delete[]:
Template
Thus, the behavior of operator new[] and operator new is the same by default, operator delete[] and operator delete are the same, except that new operator and new[] operator, delete operator, and delete[] operator. Of course, we can choose to override operator new and delete with and without "[]" according to different needs to meet different specific needs.
Change the code of the previous class MyClass slightly - 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=003A5A58
This time, new[] honestly applied for 12 bytes of memory, and the result of the application is the same as the result returned by the new[] operator. It seems that whether to add 4 bytes in front of it depends only on This class has no destructor, of course, this is not true, the correct argument is whether the class needs to call the constructor, because the following two cases, although this class did not declare the destructor, but still apply for more than 4 Byte: First, this class has a member that needs to call the destructor, and second, this class inherits from the class that needs to call the destructor. Thus, we can recursively define "the class that needs to call the destructor" in one of three situations:
Explicitly declare the destructor
2 has a member of the class that needs to call the destructor
3 inherited from the class that needs to call the destructor
Similarly, when applying a simple type of array dynamically, it will not apply for more than 4 bytes. So in both cases, using delete or delete[] when releasing memory is OK, but for good habits, we should also note that as long as it is a dynamically allocated array, use delete[] when it is released.
How to know the length when releasing memory
But at the same time, it brings a new problem. Since the application does not need to call the destructor class or the simple type of the array does not record the number of information, then operator delete, or more directly, how free () is to recover this What about memory? This is to study the structure of the memory returned by malloc(). Similar to new[], in fact, when malloc() applies for memory, it also applies for several bytes of content, but this has nothing to do with the type of the requested variable, which we pass in when calling malloc. The parameter can also understand this - it only receives the length of the memory to apply for, it does not matter what type of memory this save. Let's run this piece of code as an experiment:
Char *p = 0;for(int i = 0; i < 40; i += 4){ char* s = new char[i]; printf("alloc %2d bytes, address=%p distance=%d/ n", i, s, s - p); p = s;}
We directly look at the VC2005 Release version of the results of the operation, DEBUG version because it 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 12 bytes, address=003A3720 distance=16
Alloc 16 bytes, address=003A3738 distance=24
Alloc 20 bytes, address=003A84C0 distance=19848
Alloc 24 bytes, address=003A84E0 distance=32
Alloc 28 bytes, address=003A8500 distance=32
Alloc 32 bytes, address=003A8528 distance=40
Alloc 36 bytes, address=003A8550 distance=40
Each time the number of allocated bytes is more than the previous one, the distance value records the difference from the previous allocation. The first difference has no practical meaning, and there is a large difference in the middle. It may be that the memory has already been Was assigned, so I also ignore it. The smallest difference in the result is 16 bytes. When we apply for 16 bytes, the difference becomes 24, and there is a similar rule behind it. Then we can think of the memory structure obtained from the application as follows:
It is not difficult to see in the picture that when we want to allocate a piece of memory, the resulting memory address and the last tail address must be at least 8 bytes away (more in the DEBUG version), then we can guess that these 8 The bytes should be recorded with information about this allocated memory. Observe the contents of these 8 sections and get the result as follows:
The right-hand side is the hexadecimal representation of the contents of the 8 bytes before each assigned address. As can be seen from the red line in the figure, the first byte of the 8 bytes is multiplied by 8 to get the phase. As for the distance between two allocations, it can be seen that after the test allocates a larger length at a time, the second byte has this meaning, and it represents the upper 8 bits, that is, the first two of the 8 bytes that are empty before. The byte records the length information of the allocated memory at a time. The following six bytes may be related to the information of the free memory list, and used to provide the necessary information when translating the memory. This answers the question raised earlier. The original C/C++ has already recorded sufficient information for memory recovery when allocating memory, but we usually don't care about it.
Phone Holder,Phone Holder For Mobile Phone,Car Air Vent Phone Holder,Mobile Phone Holder
Shenzhen ChengRong Technology Co.,Ltd. , https://www.laptopstandsuppliers.com