C ++ Memory Object battle

Source: Internet
Author: User
Tags protected constructor
If a person is a programmer and has no knowledge of memory, I can tell you that he must be boasting. Writing a program in C or C ++ requires more attention to the memory, not only because the reasonable memory allocation directly affects the efficiency and performance of the program, but more importantly, when we operate on the memory, problems may occur accidentally. In many cases, these problems are hard to detect, such as memory leakage, such as hanging pointers. Today, I am not going to discuss how to avoid these problems, but to understand C ++ memory objects from another perspective.

We know that C ++ divides memory into three logical areas: heap, stack, and static storage areas. In this case, the objects in them are stack objects, stack objects, and static objects. So what are the differences between these different memory objects? What are the advantages and disadvantages of stack objects? How can I disable the creation of heap objects or stack objects? These are the theme of today.

   I. Basic Concepts

Let's take a look at the stack. Stack is generally used to store local variables or objects. For example, we use an object similar to the following statement in the function definition:

Type stack_object;

Stack_object is a stack object. Its life cycle starts from the defined point and ends when the function returns.

In addition, almost all temporary objects are stack objects. For example, the following function definition:

Type fun (type object );

This function generates at least two temporary objects. First, the parameters are passed by value. Therefore, the copy constructor is called to generate a temporary object object_copy1. In the function, objects are not used, but object_copy1. Naturally, object_copy1 is a stack object which is released when the function returns. In addition, this function returns a value. When the function returns, if we do not consider the return value optimization (nrv), a temporary object object_copy2 will also be generated. This temporary object will be released within 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 object_copy2 is generated.

The execution of the second statement above is like this. First, a temporary object object_copy2 is generated when the function fun returns, and then the value assignment operator is called to execute the statement.

Tt = object_copy2; // call the value assignment operator

Have you seen it? The compiler generates so many temporary objects for us without perception, and the time and space overhead of generating these temporary objects may be very large. Therefore, you may understand why it is better to use const reference transmission instead of passing function parameters by value for a "large" object.

Next, let's look at the heap. Heap is also called a free storage zone. It is dynamically allocated during program execution, so its biggest feature is dynamic. In C ++, the programmer is responsible for creating and destroying all heap objects. Therefore, if the heap object is not processed properly, memory problems may occur. If a heap object is assigned but you forget to release it, memory leakage occurs. if the object is released, the corresponding pointer is not set to null, this pointer is the so-called "hanging pointer". When this pointer is used again, illegal access will occur, and the program will crash in severe cases.

So how does C ++ allocate heap objects? The only method is to use new (of course, the C-type heap memory can also be obtained by using the malloc command). If new is used, a piece of memory will be allocated in the heap, and returns a pointer to the heap object.

Let's take a look at the static storage area. All static objects and global objects are allocated in the static storage area. Global objects are allocated before the main () function is executed. In fact, before executing the display code in the main () function, a _ main () function generated by the compiler is called, while _ main () the function constructs and initializes all global objects. Before the main () function ends, 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 converted into the following:

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
}

Therefore, after knowing this, we can lead to some tips. For example, if we want to make some preparations before the main () function is executed, then we can write these preparations to the constructor of a custom global object. In this way, before the explicit Code Execution of the main () function, the constructor of this global object is called to execute the expected action, which achieves our goal. I am talking about global objects in the static storage area. So, what about local static objects? A local static object is usually defined in a function, just like a stack object, but a static keyword is added before it. The life cycle of a local static object is the first time it is called from the function where it is located. More specifically, this static local object is generated when the declared code of the static object is executed for the first time, this object is not destroyed until the end of the entire program.

There is also a static object, that is, it acts as a static member of the class. In this case, some complicated problems are involved.

The first problem is the lifetime of the static member object of the class. The static member object of the class is generated with the generation of the First Class Object and disappears at the end of the entire program. In this case, we define a class in the program, which has a static object as a member, but in the program execution process, if we do not create any class object, the static object contained in the class will not be generated. Also, if multiple class objects are created, all these objects share the static object member.

The second problem is when:

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 = ...... ;

Note that the preceding three statements marked as "simhei" indicate that the s_object they access is the same object? The answer is yes. They do point to the same object. It doesn't sound true, does it? But this is a fact. You can write a simple code to verify it. What I want to do is to explain why this happens? We know that when a class such as derived1 inherits from another class such as base, it can be seen that a derived1 object contains a base-type object, which is a subobject. The general memory layout of a derived1 object is as follows:
  
Let's think about it. When we pass a derived1 object to a function that accepts a non-referenced base parameter, a cut will occur. How is this cut? I believe you already know that subobject IN THE derived1 object is taken out, and all other data members customized by derived1 are ignored, then pass the subobject to the function (in fact, the subobject copy is used in the function ).

All the objects that inherit the base class's derived classes contain a base-type subobject (this is the key to using the base-type pointer to point to a derived1 object, and naturally it is also the key to polymorphism ), all subobject and all base objects share the same s_object object. Naturally, instances of classes in the inheritance system derived from the base class share the same s_object object. Shows the object layout of example, example1, and example2 mentioned above:

   Ii. Comparison of Three memory objects

The advantage of stack objects is that they are automatically generated when appropriate and destroyed when appropriate, without the worry of programmers. In addition, stack objects are generally created at a faster speed than heap objects, when allocating heap objects, operator new operations will be called. Operator new will adopt some memory space search algorithm, and the search process may be time-consuming. It is not so troublesome to generate STACK objects, it only needs to move the top pointer of the stack. Note that the stack space is usually relatively small, usually 1 MB ~ 2 MB, so it is not suitable for allocating large objects in the stack. Note that it is best not to use stack objects in recursive functions, because as the depth of recursive calls increases, the required stack space also increases linearly. When the required stack space is insufficient, this will cause stack overflow, which will generate runtime errors.

The Creation Time and destruction time of a heap object must be precisely defined by the programmer. That is to say, the programmer has full control over the life of the heap object. We often need such an object. For example, we need to create an object that can be accessed by multiple functions, but we do not want to make it global, at this time, creating a heap object is undoubtedly a good choice, and then passing the pointer of the heap object between each function can share the object. In addition, compared with stack space, the heap capacity is much larger. In fact, when the physical memory is not enough, if a new heap object needs to be generated at this time, it usually does not produce runtime errors, but the system will use virtual memory to expand the actual physical memory.
Next let's take a 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 method is not elegant. Generally, in a fully object-oriented language, there are no global objects, such as C #, because global objects mean insecure and highly coupled, excessive use of Global Objects in programs will greatly reduce program robustness, stability, maintainability and reusability. C ++ can also completely remove global objects, but it does not end up. I think one of the reasons is to be compatible with C.

The second is the static member of the class. As mentioned above, all objects in the base class and its derived class share this static member object, therefore, when data is shared or communicated between these classes or between these class objects, such static members are undoubtedly a good choice.

A static local object is used to save the intermediate state of the function where the object is located during repeated calls. One of the most notable examples is a recursive function, we all know that recursive functions call their own functions. If a nonstatic local object is defined in a recursive function, the overhead is huge when the number of recursion times is large. This is because the nonstatic local object is a stack object. Every recursive call will generate such an object. Every time this object is returned, it will be released, such an object is only limited to the current call layer. It is invisible to the deeper nested layer and the more exposed outer layer. Each layer has its own local object and parameters.

In recursive function design, static objects can be used to replace nonstatic local objects (stack objects). This not only reduces the overhead of generating and releasing nonstatic objects during each recursive call and return, static objects can also save the intermediate state of recursive calls and can be accessed by each call layer.

   3. unexpected gains from using stack objects

As mentioned above, stack objects are created when appropriate and automatically released when appropriate, that is, stack objects are automatically managed. So what will stack objects be automatically released? First, at the end of its life cycle; second, when its function is abnormal. You may say that this is normal. It's no big deal. Yes, it's no big deal. However, as long as we go deeper, we may have unexpected gains.

Stack object. When it is automatically released, it will call its own destructor. If we encapsulate Resources in the stack object and release resources in the destructor of the stack object, the probability of resource leakage will be greatly reduced, because stack objects can automatically release resources, even when the function is abnormal. The actual process is as follows: when a function throws an exception, the so-called stack_unwinding (stack rollback) occurs, that is, the stack is exposed. Because it is a stack object, it naturally exists in the stack, therefore, in the process of stack rollback, The destructor of the stack object will be executed to release the encapsulated resources. Unless, unless an exception is thrown again during the execution of the Destructor-this possibility is very small, so it is safer to encapsulate resources with stack objects. Based on this understanding, we can create a handle or proxy to encapsulate resources. This technology is used in Intelligent pointers (auto_ptr. In this case, we hope that our resource encapsulation class can only be created in the stack, that is, to restrict the creation of instances of this resource encapsulation class in the heap.
4. Prohibit the generation of heap objects

As mentioned above, you have decided to prohibit the generation of some type of heap objects. In this case, you can create a resource encapsulation class by yourself, which can only be generated in the stack, in this way, the encapsulated resources can be automatically released in case of exceptions.

So how can we disable heap objects? We already know that the only way to generate heap objects is to use the new operation. If we do not allow the use of the new operation, it will be okay. Furthermore, operator new is called when the new operation is executed, and operator new can be reloaded. The new operator is private. For symmetry, it is best to reload operator Delete to private. Now, you may have another question. Do you not need to call new to create a stack object? Yes, no, because creating stack objects does not need to search for memory. Instead, you can directly adjust the stack pointer and press the object onto the stack. The main task of operator new is to search for the appropriate heap memory, allocate space for heap objects, as mentioned above. Okay. Let's take a look at the following sample code:

# Include <stdlib. h> // The C-type memory allocation function is required.
Class resource; // indicates the resource class to be encapsulated.
Class nohashobject
{
PRIVATE:
Resource * PTR; // point to the encapsulated Resource
... // Other data member
Void * operator new (size_t size) // non-strict implementation, for illustration only
{
Return malloc (size );
}
Void operator Delete (void * PP) // not strictly implemented, for illustration only
{
Free (PP );
}
Public:
Nohashobject ()
{
// Obtain the resources to be encapsulated and point the PTR pointer to the resource.
PTR = new resource ();
}
~ Nohashobject ()
{
Delete PTR; // release encapsulated Resources
}
};

Nohashobject is now a class for prohibiting heap objects. If you write the following code:

Nohashobject * fp = new nohashobject (); // compilation error!
Delete FP;

The above code produces a compilation error. Now that you know how to design a class to prohibit heap objects, you may have the same question as me. Is it possible that the definition of Class nohashobject cannot be changed, will it be impossible to generate heap objects of this type? No, there is still a solution. I call it "brute-force cracking ". C ++ is so powerful that you can use it to do whatever you want. The technique used here is the forced conversion of pointer types.

Void main (void)
{
Char * temp = new char [sizeof (nohashobject)];

// Force type conversion. Now PTR is a pointer to the nohashobject object.
Nohashobject * obj_ptr = (nohashobject *) temp;

Temp = NULL; // prevents the nohashobject object from being modified through the temp pointer

// Force type conversion again to direct the RP pointer to the PTR Member of the nohashobject object in the heap
Resource * Rp = (resource *) obj_ptr;

// Initialize the PTR Member of the nohashobject object pointed to by obj_ptr
Rp = new resource ();
// Now you can use the obj_ptr pointer to use the nohashobject object member in the heap.
......

Delete RP; // release resources
Temp = (char *) obj_ptr;
Obj_ptr = NULL; // prevents pointer Suspension
Delete [] temp; // release the heap space occupied by the nohashobject object.
}

The above implementation is troublesome, and this implementation method is almost never used in practice, but I still write the path, because I understand it, it is good for us to understand C ++ memory objects. What is the most fundamental of the above forced type conversion? We can understand this as follows:

The data in a piece of memory remains unchanged, and the type is the glasses we wear. When we wear a pair of glasses, we will use the corresponding types to explain the data in the memory, in this way, different interpretations get different information.

The so-called forced type conversion is actually to replace another pair of glasses and then look at the same memory data.

In addition, it should be noted that different compilers may have different layout arrangements for object member data, for example, most compilers arrange the PTR pointer member of nohashobject In the first 4 bytes of the object space to ensure that the conversion action of the following statement is executed as expected:

Resource * Rp = (resource *) obj_ptr;

However, not all compilers do.

Since we can prohibit the generation of some type of heap objects, can we design a class so that it cannot generate STACK objects? Of course.

   5. Prohibit stack object generation

As mentioned above, when a stack object is created, the top pointer of the stack is moved to a space of an appropriate size, then, the corresponding constructor is called directly in this space to form a stack object. When the function returns, the corresponding constructor is called to release the object, then adjust the stack top pointer to reclaim the stack memory. In this process, the operator new/delete operation is not required. Therefore, setting operator new/Delete to private cannot be achieved. Of course, from the above description, you may have thought of setting the constructor or destructor as private so that the system cannot call the constructor or destructor, of course, you cannot generate objects in the stack.

This is indeed true, and I plan to adopt this solution. But before that, we need to make it clear that if we set the constructor to private, we cannot use new to directly generate heap objects, because New will also call its constructor After allocating space for the object. Therefore, I plan to only set the destructor to private. In addition to limiting the generation of stack objects, does setting the Destructor private affect the generation of stack objects? Yes, this also limits inheritance.

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

To restrict stack objects without limiting inheritance, we can declare the Destructor as protected, so that the two are both beautiful. The following code is used:

Class nostackobject
{
Protected:
~ Nostackobject (){}
Public:
Void destroy ()
{
Delete this; // call to protect destructor
}
};

Next, you can use the nostackobject class as follows:

Nostackobject * hash_ptr = new nostackobject ();
... // Operate on the object pointed to by hash_ptr
Hash_ptr-> destroy ();

Haha, isn't it a little strange? We use new to create an object, but not delete it, but use the destroy method. Obviously, users are not used to this weird method of use. So I decided to set the constructor to private or protected. This goes back to the problem that we tried to avoid, that is, we don't need new. How can we generate an object? We can do this indirectly by providing a static member function for this class to generate heap objects of this type. (The Singleton mode in the design mode can be implemented in this way .) Let's take a look:

Class nostackobject
{
Protected:
Nostackobject (){}
~ Nostackobject (){}
Public:
Static nostackobject * creatinstance ()
{
Return new nostackobject (); // call the protected Constructor
}
Void destroy ()
{
Delete this; // call the protected destructor
}
};

Now we can use the nostackobject class as follows:

Nostackobject * hash_ptr = nostackobject: creatinstance ();
... // Operate on the object pointed to by hash_ptr
Hash_ptr-> destroy ();
Hash_ptr = NULL; // prevents pointer Suspension

Now it feels better. The operations for generating and releasing objects are the same.

Okay. Here, we have already covered a lot of things. If we want to make the memory object more in-depth and comprehensive, we may need to write a book. In my own skill, it may be difficult to fully grasp. If what I wrote above can help you gain something or inspire you, I will be satisfied. If you want to learn more about memory objects, I recommend you read the book "exploring the C ++ Object Model in depth.

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.