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


Related articles: