Introduction
When you first see this question, you may have some questions: Is there anything to say about creating C ++ class objects? Isn't it just calling Constructors? In fact, the situation is not as simple as imagined. A large number of details are hidden or ignored, and these details are the key to solving some other problems, therefore, it is necessary for us to go deep into this "mysterious" area and explore little-known secrets.
Allocate space (Allocation)
The first step to create a C ++ class object is to allocate memory space for it. For global objects, static objects, and objects allocated to the stack area, the memory allocation is completed at the compilation stage. For Objects allocated to the stack area, they are dynamically allocated during running. The memory space allocation process involves two key issues:
- The size of the space to be allocated, that is, the size of the Class Object. This problem is not a problem for the compiler, because the size of the Class Object is determined by it. It is the most clear about how much memory to allocate.
- Whether there is enough memory space for allocation. For different situations, we need to analyze specific issues:
- Global and static objects. The compiler will divide them into independent segments (Global segments) and allocate enough space for them. This generally does not involve insufficient memory space.
- Objects allocated in the stack area. The size of the stack area is determined by the compiler settings. Regardless of the specific settings, it always has a specific value, so the stack space is limited, allocating a large number of objects in the stack area at the same time will cause stack area overflow, because the stack area allocation is completed in the compilation phase, therefore, an exception in the compilation phase will be thrown when the stack area overflows.
- Objects allocated in the heap area. The heap memory space is allocated at run time. Because the heap space is also limited, attempts to allocate a large number of objects simultaneously in the stack area will lead to allocation failure, normally, a running exception is thrown or a meaningless value (usually 0) is returned ).
Initialization)
This stage is the most mysterious and easily overlooked stage in the process of object creation. To understand the specific tasks completed in this phase, the key is to distinguish two confusing concepts: initialization and assignment ). Initialization is performed with the birth of an object before the value assignment. The value assignment gives an object a new value after it is generated. Here I think of a good example: any baby born in a hospital will give it a mark when it is born to prevent confusion with other babies, this identifier is usually the number of the bed where the mother of the baby is located. The process of giving the baby an identifier to the hospital can be considered initialization. Of course, when a baby's parents get a name for them, the process of naming can be considered as a value assignment. After initialization and assignment, other people can identify them by their names. After distinguishing these two concepts, we will go to the analysis of object initialization. Class object initialization is actually to initialize all data members in the class object. C ++ has provided us with the ability to initialize class objects. We can implement this by implementing the initialization list of Constructor (member initialization list. Is this the case? Let's take a look at the specific situation. I wrote two simple classes:
Class cinnerclass {
Public:
Cinnerclass (int id): m_iid (ID ){}
Cinnerclass & operator = (const cinnerclass & Rb ){
M_iid = RB. m_iid;
Return * this;
}
PRIVATE:
Int m_iid;
};
Class cjdbase {
Public:
Cjdbase: cjdbase (int id): m_innerobj (ID), m_iid (ID ){
M_innerobj = 10;
}
PRIVATE:
Cinnerclass m_innerobj;
Int m_iid;
};
Let's focus on the cjdbase class constructor. The constructor of the cjdbase class provides an initialization list to initialize its member variables. The corresponding assembly code is as follows (Note: I only keep the key code ):
MoV dword ptr _ this $ [EBP], ECx
MoV eax, dword ptr _ ID $ [EBP]
Push eax
MoV ECx, dword ptr _ this $ [EBP]
Call ?? 0cinnerclass @ Qae @ H @ Z; cinnerclass: cinnerclass
MoV eax, dword ptr _ this $ [EBP]
MoV ECx, dword ptr _ ID $ [EBP]
MoV dword ptr [eax + 4], ECx
; 5: m_innerobj = 10;
Push 10; 0000000ah
Lea ECx, dword ptr $ t1359 [EBP]
Call ?? 0cinnerclass @ Qae @ H @ Z; cinnerclass: cinnerclass
Lea eax, dword ptr $ t1359 [EBP]
Push eax
MoV ECx, dword ptr _ this $ [EBP]
Call ?? 4cinnerclass @ qaeaav0 @ abv0 @ Z; cinnerclass: Operator =
From this assembly code, we can see some meaningful content:
- The initialization list is executed before the code in the constructor body;
- The initialization list does execute the initialization process of the data member. This can be seen from the call of the constructor of the member object.
Assignment)
After the object is initialized, we can still assign values to it. Like class object initialization, Class Object assignment is to assign values to all data members in class objects. C ++ has also provided us with this capability, which can be achieved through the implementation body of the constructor (that is, the part of the constructor wrapped. This can also be confirmed by calling the value assignment operator (operator =) of member objects in the assembly code above.
End
With the last line of code executed by the constructor, it can be said that the process of creating class objects will be completed successfully. From the above analysis, we can see that the constructor implements the initialization and value assignment processes of the object. The initialization of the object is done through the initialization list, and the assignment of the object is done through the constructor, or, more accurately, it should be the implementation body of the constructor.
Virtual function table pointer (vtable pointer)
How can we ignore the virtual function table pointer? Without it, the C ++ world would be much more clean. What we are most concerned about is when the virtual function table pointer values are assigned to Classes with virtual functions and their class objects? We do not have any code or capabilities (except for brute-force cracking). We can assign values to the virtual table pointers when creating class objects, the compiler secretly assigns values to virtual table pointers. Here, we often ignore the details: Does the compiler assign a value to the virtual table pointer before it enters the constructor body or inside the constructor body? Let's take a look at the specific situation. Add another virtual function based on the above cjdbase class:
Class cjdbase {
Public:
Cjdbase: cjdbase (int id): m_innerobj (ID), m_iid (ID ){
M_innerobj = 10;
}
Public:
Virtual void dumpme (){}
PRIVATE:
Cinnerclass m_innerobj;
Int m_iid;
};
Use vs2002 to compile and obtain the assembly code of this constructor. The most critical code is as follows:
MoV dword ptr _ this $ [EBP], ECx
MoV eax, dword ptr _ this $ [EBP]
MoV dword ptr [eax], offset flat :?? _ 7cjdbase @ 6B @
MoV eax, dword ptr _ ID $ [EBP]
Push eax
MoV ECx, dword ptr _ this $ [EBP]
Add ECx, 4
Call ?? 0cinnerclass @ Qae @ H @ Z; cinnerclass: cinnerclass
MoV eax, dword ptr _ this $ [EBP]
MoV ECx, dword ptr _ ID $ [EBP]
MoV dword ptr [eax + 8], ECx
; 5: m_innerobj = 10;
Push 10; 0000000ah
Lea ECx, dword ptr $ t1368 [EBP]
Call ?? 0cinnerclass @ Qae @ H @ Z; cinnerclass: cinnerclass
Lea eax, dword ptr $ t1368 [EBP]
Push eax
MoV ECx, dword ptr _ this $ [EBP]
Add ECx, 4
Call ?? 4cinnerclass @ qaeaav0 @ abv0 @ Z; cinnerclass: Operator =
From
MoV dword ptr [eax], offset flat :?? _ 7cjdbase @ 6B @
We can clearly see that, at the very beginning of the constructor, before entering the constructor body, or even before entering the initialization list, the compiler inserts code and assigns a value to the virtual table pointer with the virtual table address of the class being constructed.
Postscript
It is hard to imagine that the process of creating a simple class object contains so many secrets if you do not practice and analyze it yourself. Knowing these secrets opens the door to victory for us to solve other problems.
I will try some of the following questions. I wonder if you will be able to feel enlightened after reading this article:
1. Why does C ++ need to provide an initialization list? In which cases must the initialization list be implemented? (Note: In some cases, values can only be initialized but cannot be assigned)
2. Can constructor be a virtual function? What are the results of calling a virtual function in the constructor? (Note: The virtual table pointer is initialized at the beginning of the constructor)
3. What is the difference between constructor and the value assignment operator =? (Tip: differentiate between initialization and assignment)
History
07/29/2007 V1.0
First version of the original article