Analysis of the use of default constructors in c++ from assembly
- 2020-04-01 21:39:07
- OfStack
C++ in the source program:
class X {
private:
int i;
};
int main() {
X x;
}
The class X above does not define a constructor, just an int I.
The following is its assembler:
; 7 : int main() {
push ebp;ebp For a register that always points to the bottom of a function call stack, as the base address, the offset is used to access the variables on the call stack, but there are no variables to access, so it does not work
mov ebp, esp; The purpose of these two sentences is to save the call main The base address of the previous stack ebp And will ebp Point to the main The bottom of the call stack
push ecx; To register ecx The value of the stack , The stack pointer esp To move forward 4byte
; The function of this sentence is reserved for the object to be created 4byte And write into it ecx The value of the
; 8 : X x;
; 9 : }
xor eax, eax;eax It's also a register, it doesn't work here
mov esp, ebp; Moves the stack top pointer to push ecx The forward position, that is released 4byte The space of
pop ebp; Restore the base address to main The state before the call
ret 0; The function returns
Assembly found that by pushing the ecx, the compiler moved the top of the stack by 4 bytes and wrote the ecx value of the register. Class X contains only an int and is exactly 4 bytes in size, so this can be considered as allocating space for object X. And then there's no function call to initialize this area properly. As a result, there are no initialization operations without explicitly defining a constructor.
Here's another c++ program:
class X {
private:
int i;
int j;//Add a member variable int j
};
int main() {
X x;
}
Compared to the above, a member variable int j is added to class X and the size of the class becomes 8 bytes.
The corresponding sink code is as follows:
; 8 : int main() {
push ebp
mov ebp, esp
sub esp, 8; Stack top pointer moves 8byte Is exactly equal to the class X The size of the
; 9 : X x;
; 10 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
As can be seen from the pool code, the stack does indeed set aside 8 bytes through the sub esp 8 instruction, which is exactly the size of class X, again without calling any function for initialization.
So, to sum up, the compiler doesn't have any function calls to initiate operations when a class doesn't explicitly define a constructor, it just moves the top of the stack to make room for the object, which means it doesn't provide a default constructor at all.
So what does the book say about the compiler providing the default constructor?
Now, in the first case, the class has a virtual member function:
C++ source code as follows:
class X {
private:
int i;
int j;//Add a member variable int j
public:
virtual ~X() {
}
};
int main() {
X x;
}
The destructor is a virtual function
The following is the assembly code corresponding to the main function:
; 13 : int main() {
push ebp
mov ebp, esp
sub esp, 12 ; For the object x The reserved 12byte Space, member variables int i . int j Account for 8byte , because there is a virtual function, so vptr Pointer to account for 4byte
; 14 : X x;
lea ecx, DWORD PTR _x$[ebp]; To obtain x The first address of the object, stored ecx register
call ??0X@@QAE@XZ; This call x Constructor of
; 15 : }
lea ecx, DWORD PTR _x$[ebp]; Access to the object x The first address
call ??1X@@UAE@XZ ; Call the destructor
xor eax, eax
mov esp, ebp
pop ebp
ret 0
As you can see, the constructor for object x is called, and the compiler does indeed synthesize the default constructor.
The following is the assembly code of the constructor:
??0X@@QAE@XZ PROC ; X::X, COMDAT
; _this$ = ecx
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _this$[ebp], ecx;ecx Register store object x The first address
mov eax, DWORD PTR _this$[ebp]; The object x To register eax
mov DWORD PTR [eax], OFFSET ??_7X@@6B@; Set up here vptr The value of the pointer, pointing to vtable (OFFSET ??_7X@@6B@ Is to obtain vtable The address of the )
; And you can also prove it by this sentence vptr The pointer is at the actual address of the object
mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret 0
As you can see, since there are virtual functions, involving polymorphism, the construct initializes the VPTR pointer, but does not assign values to the other two variables int I and int j.
As you can see from the above, the compiler does provide a default constructor when the class contains virtual functions without explicitly defining the constructor. Therefore, when a class inherits from an imaginary base class, the above situation is also satisfied.
In the second case, class Y inherits from class X, which explicitly defines a default constructor (not provided by the compiler), and class Y does not define any constructors:
First to look at c++ source code:
class X {
private:
int i;
int j;
public:
X() {//X shows the default constructor defined
i = 0;
j = 1;
}
};
class Y : public X{//Y inherits from X
private:
int i;
};
int main() {
Y y;
}
Class Y does not show any constructors defined
The following is the assembly code corresponding to the main function:
; 19 : int main() {
push ebp
mov ebp, esp
sub esp, 12 ; For the object y The reserved 12byte Space, y Self member variable int i Account for 4byte Member variables in the parent class int i int j Account for 8byte
; 20 : Y y;
lea ecx, DWORD PTR _y$[ebp]; Access to the object y Is stored in a register ecx
call ??0Y@@QAE@XZ; Call object y Constructor of
; 21 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
The default constructor for the default y object provided by the compiler is called in the main function.
The following is the compiler provided the default constructor of y object assembly code:
??0Y@@QAE@XZ PROC ; Y::Y, COMDAT
; _this$ = ecx
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _this$[ebp], ecx;ecx In object y The first address
mov ecx, DWORD PTR _this$[ebp]
call ??0X@@QAE@XZ ; Call the parent class X Constructor of
mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret 0
??0Y@@QAE@XZ ENDP
You can see that the constructor of the y object calls the constructor of the parent class to initialize the member variables inherited from the parent class, but the member variables themselves are still not initialized.
The following is the constructor assembly code of superclass X:
; 7 : X() {
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _this$[ebp], ecx; ecx In object y The first address
; 8 : i = 0;
mov eax, DWORD PTR _this$[ebp]; object y First address to register eax
mov DWORD PTR [eax], 0; Initializes variables in the parent class i
; 9 : j = 1;
mov ecx, DWORD PTR _this$[ebp]; object y First address to register ecx
mov DWORD PTR [ecx+4], 1; Initializes variables in the parent class j, In the object y Memory space, starting from the first address 8 Bits are used to store member variables inherited from the parent object, after 4byte To store its own member variables
; Because the first address stores a superclass member variable i Therefore, the memory address should be from the object y The first address of 4byte , to find the parent class member variable j The location
; 10 : }
mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret 0
As you can see, the member variables that the y object inherits from its parent class are initialized by the parent class constructor. The parent object is contained in the child object, and the first address stored by this pointer, register ecx, is always the first address of the child object y.
What if there are no constructors defined in the superclass X?
The following is the c++ source code:
class X {
private:
int i;
int j;
};
class Y : public X{//Y inherits from X
private:
int i;
};
int main() {
Y y;
}
Neither the parent nor the subclass has any constructors.
Here is the main function assembly code:
; 16 : int main() {
push ebp
mov ebp, esp
sub esp, 12 ; Same as before, object y The reserved 12byte
; 17 : Y y;
; 18 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
You can see that there are no function calls in main at all, that is, the compiler does not provide a default constructor for child object y.
What if there is a constructor with arguments in the parent class and no constructor in the subclass? At this point the compiler will report an error.
Now for the third case, class Y contains a member object X, which has a default constructor for the display definition, while class Y does not have any constructor:
First look at c++ source code:
The same code at the page code block index 12
Class X is a member object of class Y
The following is the assembly code of the main function:
; 21 : int main() {
push ebp
mov ebp, esp
sub esp, 12 ; For the object y The reserved 12byte Variable accounting for a member object 8byte object y Student: self occupation variable occupation 4byte The member object is contained in the object y In the
; 22 : Y y;
lea ecx, DWORD PTR _y$[ebp]; object y The first address of the deposit ecx
call ??0Y@@QAE@XZ; Call object y Is the default constructor provided by the compiler
; 23 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
The constructor for object y is called, that is, the compiler provides the default constructor
Constructor assembly encoding of object y:
??0Y@@QAE@XZ PROC ; Y::Y, COMDAT
; _this$ = ecx
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _this$[ebp], ecx;ecx In object y The first address
mov ecx, DWORD PTR _this$[ebp]
add ecx, 4; add 4 Because of the object y At the beginning of the first address is stored its own member variables i
call ??0X@@QAE@XZ ; Invoke member object x Constructor of
mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret 0
The constructor of object y calls the constructor of the member object x to initialize the member variable in the member object. The member variable of object y itself is not initialized.
Constructor assembly encoding of member object x:
??0X@@QAE@XZ PROC ; X::X, COMDAT
; _this$ = ecx
; 7 : X() {
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _this$[ebp], ecx;ecx Contains a member object x Starting address
; 8 : i = 0;
mov eax, DWORD PTR _this$[ebp]; Members of the object x The starting address of eax register
mov DWORD PTR [eax], 0; Initializes the member object x Middle member variable i
; 9 : j = 0;
mov ecx, DWORD PTR _this$[ebp]; Members of the object x The starting address of ecx register
mov DWORD PTR [ecx+4], 0; Initializes the member object x Middle member variable j add 4 The reason is that j Is the address of the member object x The starting address 4byte( Member object x Member variable of i The number of bytes )
; 10 : }
mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret 0
But what if the member object x also doesn't have any constructors?
The following is the c++ source code:
class X {
private:
int i;
int j;
};
class Y {
private:
int i;
X x;//X member object
};
int main() {
Y y;
}
Here is the main function assembly code:
; 17 : int main() {
push ebp
mov ebp, esp
sub esp, 12 ; Reserved for objects 12byte space
; 18 : Y y;
; 19 : }
xor eax, eax
mov esp, ebp
pop ebp
ret 0
As you can see, there are no function calls in the main function, which means that the compiler does not provide a default constructor.
What if the member object x has a constructor with arguments (that is, not the default constructor) and the object y has no constructor? At this point, the compiler reports an error.
This situation is very similar to the previous one.
From the above, it can be concluded that there are three cases when a class contains no constructors and the compiler provides a default constructor:
1 Class itself function virtual member function or inherit from virtual base class
2 The base class of the class has a constructor, and the base class constructor still shows the default constructor defined (provided by the non-compiler), if the constructor of the base class has parameters (that is, the non-default constructor), the compiler reports an error
3 This is similar to the previous case, where the member object of the class has a constructor, and the constructor of the member object is the default constructor of the display definition (provided by the non-compiler). If the constructor of a member object takes an argument (that is, not the default constructor), the compiler reports an error.
The above reference "VC++ in-depth explanation" inside the knowledge point, and their own analysis, welcome to point