c++--memory objects prohibit the generation of heap objects against the generation of stack objects

Source: Internet
Author: User

Write programs in C or C + +, need more attention to memory, this is not only because the allocation of memory is reasonable directly affect the efficiency and performance of the program, more important, when we operate the memory when the problem is accidentally, and many times, these problems are not easy to detect, such as memory leaks, such as hanging hands.

We know that C + + divides memory into three logical regions: heap, stack, and static storage. In this case, I call the objects in them the heap object, the Stack object, and the static object, respectively. So what's the difference between these different memory objects? What are the pros and cons of heap objects and stack objects? How do I disable the creation of heap objects or stack objects?

One. The basic concept
to look at the stack first. The
stack, which is typically used to hold local variables or objects, as we declare in function definitions like the following:
Type stack_object;
Stack_object is a stack object whose lifetime is defined by the point at which the function is returned. Life is over.

In addition, almost all temporary objects are stack objects. For example, the following function definition:
Type Fun (type Object);

This function produces at least two temporary objects, first of all, the parameters are passed by value, so the copy constructor is called to generate a temporary object object_copy1, used within the function is not using object, but object_copy1, natural, object_ Copy1 is a stack object that is released when the function returns, and this function is returned by a value, and if we do not consider the return value optimization (NRV) When the function returns, a temporary object Object_copy2 will be released for a period of time after the function returns. For example, a function has the following code:
Type TT, result;//Generate two Stack objects
TT = Fun (TT);//When the function returns, a temporary object is generated object_copy2

Execution of the second statement above is the case , first a temporary object object_copy2 is generated when the function fun is returned, and then the assignment operator is called to execute
TT = OBJECT_COPY2;//Call assignment operator
See? The compiler generates so many temporary objects for us without our senses, and the overhead of generating these temporary objects can be significant, so you might understand why it is best to use a const reference pass instead of a function parameter passed by value for a "large" object (when a const reference is passed , the direct operation inside the function is that the const reference itself does not need a copy of the variable).

Next, look at the heap. The
Heap, also known as free storage, is dynamically allocated during the execution of the program, so its greatest feature is its dynamic nature. In C + +, it is up to the programmer to create and destroy all heap objects, so if the processing is not good, memory problems occur. If you assign a heap object and forget to release it, a memory leak is generated, and if the object is disposed, but the corresponding pointer is not set to NULL, the pointer is called a "hanging pointer", and when the pointer is reused, illegal access occurs, causing the program to crash when it is heavily used.
So, how is the heap object allocated in C + +? The only way to do this is to use new (of course, the C-heap memory is also available with the class malloc directive), and whenever you use new, a chunk of memory is allocated to the heap and a pointer to that heap object is returned.

Then take a look at the static storage area.
All static objects, global objects are allocated at the static store. About global objects is allocated before the main () function is executed. In fact, before the display code in the main () function executes, a compiler-generated _main () function is called, and the _main () function performs the construction and initialization of all global objects. By the end of the main () function, the Exit function generated by the compiler is called to release all global objects. For example, the following code:
void Main (void)
{
...//Explicit code
}

In fact, it is transformed into this:
void Main (void)
{
_main (); Implicit code, generated by the compiler, to construct all global objects
...//Explicit code
... ...
Exit (); Implicit code, generated by the compiler, to release all global objects
}

So, after knowing this, we can draw some tips, such as assuming that we want to do some preparatory work before the main () function executes, then we can write these preparations into a custom global object constructor, so that before the explicit code of the main () function is executed, The constructor of this global object will be called to perform the expected action, thus achieving our goal.
Just talking about the global object in the static store, what about the local static object? A local static object is usually defined in a function, just like a stack object, except that it has a static keyword in front of it. The lifetime of a local static object is first called from its function, or, more precisely, when the declaration code of the static object is first executed, resulting in the static local object, which is destroyed until the end of the entire program.

There is also a static object, which is a static member of class. When considering this situation, some more complex problems are involved.

The first problem is the lifetime of a class's static member object, and the static member object of class is generated with the first class object, and dies at the end of the program. That is, in a program we define a class that has a static object as a member, but in the execution of the program, if we do not create any of the class object, then the static object contained in the class is not produced. Also, if more than one class object is created, then all of these objects share that static object member.

The second problem is when the following conditions occur:
Class base{
Public
Static Type S_object;
}
Class Derived1:public base{//Public inheritance
...//Other data
}
Class Derived2:public base{//Public inheritance
...//Other data
}

Base example;
Derivde1 example1;
Derivde2 example2;
Example.s_object = ...;
Example1.s_object = ...;
Example2.s_object = ...;

Notice the last three statements above, and the s_object they are accessing is the same object? The answer is yes, they really point to the same object, which doesn't sound like true, does it? But this is the fact that you can write your own piece of simple code verification.
What I'm going to do is explain why this is going to happen. We know that when a class, such as Derived1, inherits from another class such as base, it can be seen as a Derived1 object that contains a base type object, which is a subobject.

Let's think about cutting when we pass a Derived1 object to a function that accepts a non-reference base parameter. Believe now that you know that you are simply removing the subobject from the Derived1 object, ignoring all the other data members of the Derived1 custom, and passing the subobject to the function (in fact, A copy of this subobject is used in the function.

All objects that inherit the base class's derived classes contain a base type of subobject (which is the key to a Derived1 object with a base pointer, which is naturally the key to polymorphism). All subobject and all base objects share the same S_object object, and naturally, an instance of the class in the entire inheritance system derived from the base class will share the same S_object object.



Two Comparison of three memory objects

The advantage of the stack object is automatically generated at the appropriate time, and automatically destroyed at the appropriate time, do not need the programmer to worry about, and the stack object is usually created faster than the heap object, because when the heap object is allocated, the operator new operation is called, operator new uses some kind of memory space search algorithm, While the search process can be time-consuming, the resulting stack object is not so troublesome, it just needs to move the top pointer on the stack. However, it is important to note that the size of the stack space is generally small, generally 1mb~2mb, so the larger objects are not suitable for allocation in the stack. It is important to note that the recursive function is best not to use the Stack object, because as the recursive call depth increases, the required stack space will also increase linearly, when the required stack space is not enough, it will cause the stack overflow, resulting in a run-time error.

The heap object, which is generated and destroyed at all times, is precisely defined by the programmer, which means that the programmer has complete control over the life of the heap object. We often need such objects, for example, we need to create an object that can be accessed by multiple functions, but do not want to make it global, then it is a good choice to create a heap object at this time, and then pass the pointer of this heap object between each function, can realize the sharing of this object. In addition, the heap capacity is much larger than the stack space. In fact, when there is not enough physical memory, if you need to generate a new heap object at this point, you usually do not produce a run-time error, but the system uses virtual memory to extend the actual physical memory.

Next look at the static object.
The first is the global object. Global objects provide the simplest way for inter-class communication and inter-function communication, although this approach is not elegant. In general, in a complete object-oriented language, there is no global object, such as C #, because global objects mean unsafe and high coupling, too much use of global objects in programs will greatly reduce program robustness, stability, maintainability, and reusability. C + + can also completely eliminate the global object, but in the end, I think one of the reasons is to be compatible with C.
Second is the static member of the class, as mentioned above, all objects of the base class and its derived classes share this static member object, so static members are a good choice when it is necessary to share or communicate data between these classes or between these class objects.
Then there is a static local object, which can be used to hold the intermediate state of the function that the object is in, and one of the most notable examples is the recursive function, and we all know that the recursive function calls its own function, if a nonstatic local object is defined in the recursive function, So when the number of recursion is quite large, the overhead is huge. This is because the nonstatic local object is a stack object, and each recursive invocation produces an object that, when returned, releases the object, and that such objects are confined to the current call layer and are not visible to the deeper nesting layers and the more bluntly outer layers. Each layer has its own local objects and parameters. In a recursive function design, you can use a static object instead of the nonstatic local object (that is, the Stack object), which not only reduces the overhead of generating and releasing the Nonstatic object per recursive call and return, but also the static object can hold the middle state of the recursive call. and can be accessed by each call layer.



Three Unexpected harvesting using stack objects

As already mentioned, the stack object is created at the appropriate time and then automatically released at the appropriate time, that is, the stack object has automatic management function. So when will the Stack object be released automatically? First, at the end of its lifetime; second, when the function in which it is located has an exception. You may say, these are normal, no big deal. Yes, it's no big deal. But as soon as we go a little deeper, there may be an unexpected harvest.

The Stack object, when released automatically, calls its own destructor. If we encapsulate the resource in the Stack object and perform the action of releasing the resource in the destructor of the stack object, the probability of the resource leak is greatly reduced, because the stack object can automatically release the resource, even if the function has an exception.
The actual process is this: when a function throws an exception, a so-called stack_unwinding (stack rollback) occurs, that is, the stack expands, because the stack object is naturally present in the stack, so during the stack rollback, the destructor of the stack object is executed to release the resources it encapsulates. It is more secure to encapsulate a resource with a stack object unless the exception is thrown again during the execution of the destructor-and this possibility is small. Based on this understanding, we can create our own handles or proxies to encapsulate the resources. This technique is used in smart pointers (AUTO_PTR). When this is needed, we want our resource encapsulation class to be created only in the stack, that is, to restrict the creation of instances of the resource encapsulation class in the heap.


Four Prohibit the generation of heap objects

As mentioned above, you decide to prohibit the generation of some type of heap object, you can create a resource encapsulation class yourself, the class object can only be generated in the stack, so that the encapsulated resources can be automatically freed in the event of an exception.

So how do you prohibit the generation of heap objects? We already know that the only way to produce a heap object is to use the new operation, and if we forbid the use of new, it's OK. Further, when the new operation executes, operator new is called, and operator new can be overloaded. Method has, is to make new operator private, for symmetry, it is best to operator delete also overloaded to private. Now, you may have questions again, don't you need to call new to create a stack object? Yes, no, because creating a stack object does not need to search for memory, but instead directly adjusts the stack pointer, pushes the object to the stack, and operator new's main task is to search for the appropriate heap memory and allocate space for the heap objects, as mentioned above. OK, let's take a look at the following sample code:

#include <stdlib.h>//C-memory allocation function required
Class Resource; Represents the resource class that needs to be encapsulated
Class Nohashobject {
Private
resource* ptr;//point to the encapsulated resource
...//Other data members
void* operator new (size_t size) {//not strictly implemented, only for illustrative purposes
return malloc (size);
}
void operator Delete (void* pp) {//not strictly implemented, only for illustrative purposes
Free (PP);
}
Public
Nohashobject () {
This is where you get the resources you need to encapsulate and point the PTR pointer to the resource
ptr = new Resource ();
}
~nohashobject () {
Delete ptr; To release encapsulated resources
}
};

Nohashobject is now a class that prohibits heap objects, if you write the following code:
nohashobject* fp = new Nohashobject (); Compile time error!
Delete FP;

The above code will produce a compile-time error. Well, now that you know how to design a class that prohibits heap objects, you may have the same question as I do, that if the definition of a class Nohashobject cannot be changed, it must not produce the heap object of that type? No, there is a way, I call it the "brute force law." C + + is so powerful and powerful that you can use it to do anything you want to do. The main use here is that the technique is the coercion of the pointer type.
void Main (void) {
char* temp = new char[sizeof (nohashobject)];

Coercion type conversion, now PTR is a pointer to a Nohashobject object
nohashobject* obj_ptr = (nohashobject*) temp;

temp = NULL; Prevent Nohashobject objects from being modified by the temp pointer

Once again, force the type conversion so that the RP pointer points to the PTR member of the Nohashobject object in the heap
resource* RP = (resource*) obj_ptr;

Initializes the PTR member of the Nohashobject object that obj_ptr points to
RP = new Resource ();
You can now use the Nohashobject object members in the heap by using the obj_ptr pointer
... ...

Delete RP;//Release resources
temp = (char*) obj_ptr;
Obj_ptr = NULL;//prevent hanging pointer generation
delete [] temp;//Releases the heap space occupied by the Nohashobject object.
}

The implementation above is cumbersome, and this implementation is rarely used in practice, but I still write the antecedents because it is good for us to understand C + + memory objects.
What is the most fundamental of all the forced-type conversions above? We can understand this:
The data in a block of memory is constant, and the type is the one we wear, and when we wear a pair of glasses we use the corresponding type to interpret the data in memory so that different interpretations get different information.
The so-called coercion type conversion is actually the same piece of memory data that is replaced by another pair of glasses.

It is also to be reminded that the layout of the member data of the object may be different from one compiler to another, for example, most compilers arrange the nohashobject ptr pointer members in the first 4 bytes of the object space, so that the conversion action of the following statement is performed as we expected:
resource* RP = (resource*) obj_ptr;

However, this is not necessarily true of all compilers.
Since we can prohibit the generation of some type of heap object, can you design a class so that it doesn't produce a stack object? Of course.



Five Prohibit generation of Stack objects

As mentioned earlier, when you create a stack object, you move the stack-top pointer to "move out" the appropriate size space, and then call the corresponding constructor directly on that space to form a stack object, and when the function returns, it calls its destructor to release the object, and then adjusts the stack-top pointer to retract that stack of memory. In this process is not required operator New/delete operation, so the operator New/delete set to private can not achieve the purpose. Of course, from the above narrative, you may have thought of: the constructor or destructor is set to private, so that the system can not call the construction/destructor, of course, can not generate objects in the stack.

This is indeed possible, and I intend to adopt such a scheme. But before this, one thing to think about is that if we set the constructor to private, then we won't be able to create the heap object directly with new, because new will also call its constructor when it allocates space for the object. So, I'm going to just set the destructor to private. Further, will the destructor be set to private in addition to restricting the generation of stack objects, there are other effects? Yes, this will also limit inheritance.

If a class is not intended to be a base class, the usual scenario is to declare its destructor as private.

To limit stack objects, but not to restrict inheritance, we can declare destructors as protected, which makes the most of both worlds. As shown in the following code:
Class Nostackobject
{
Protected
~nostackobject () {}
Public
void Destroy ()
{
Delete this;//call Protection destructor
}
};

Next, you can use the Nostackobject class like this:
nostackobject* hash_ptr = new Nostackobject ();
...//manipulate the objects pointed to by hash_ptr
Hash_ptr->destroy ();

Oh, is not feel a bit strange, we use new to create an object, but not with delete to delete it, but to use the Destroy method. Obviously, the user is not accustomed to this weird way of using. So, I decided to set the constructor to private or protected. This goes back to the problem that I tried to avoid, that is, without new, what is the way to generate an object? We can do this in an indirect way, which is to have this class provide a static member function that is specifically used to produce the heap object of that type. (The singleton pattern in design mode can be implemented in this way.) Let's take a look at:
Class Nostackobject
{
Protected
Nostackobject () {}
~nostackobject () {}
Public
Static nostackobject* creatinstance () {
return new Nostackobject ();//constructor to invoke protection
}
void Destroy () {
Delete this;//destructor to call protection
}
};

Now you can use the Nostackobject class like this:
nostackobject* hash_ptr = Nostackobject::creatinstance ();
...//manipulate the objects pointed to by hash_ptr
Hash_ptr->destroy ();
Hash_ptr = NULL; Prevent the use of hanging pointers

Now it doesn't feel much better, and the action to build the object and release the object is consistent.

OK, speaking here, already involved in more things, if you want to make the memory object more thorough and comprehensive, it may need to write a book, and in my own skill, it may be difficult to fully grasp. If what you have written above will give you something to gain or inspire, I will be satisfied. If you want to go further and learn more about memory objects, then I recommend you look at the book "Exploring the C + + object model in depth".

c++--memory objects prohibit the generation of heap objects against the generation of stack objects

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.