The application of polymorphism in c++ from assembly

  • 2020-04-01 23:38:38
  • OfStack

In c++, a class is polymorphic when it contains virtual functions. An important function of the constructor is to initialize the VPTR pointer, which is a key step to ensure polymorphism.
The constructor initializes the VPTR pointer
The following is the c++ source code:

class X {
private:
    int i;
public:
    X(int ii) {
        i = ii;
    }
    virtual void set(int ii) {//Virtual functions
        i = ii;
    }
};
int main() {
   X x(1);
}

The following is the code of the corresponding main function:

_main    PROC
; 16   : int main() {
    push    ebp
    mov    ebp, esp
    sub    esp, 8; For the object x The reserved 8byte space  vptr Pointer to account for 4 byte   Member variables i Account for 4byte
; 17   :    X x(1);
    push    1;//Push 1 and pass it to the constructor as an argument
    lea    ecx, DWORD PTR _x$[ebp];//Gets the first address of x, this pointer, passed to the constructor as an implicit parameter
    call    ??0X@@QAE@H@Z                ;  for x Call constructor 
; 18   : }
    xor    eax, eax
    mov    esp, ebp
    pop    ebp
    ret    0
_main    ENDP

As you can see from the assembly code, since class X has a virtual function, the main function reserves an 8byte space on the stack for object X to hold the VPTR pointer and the member variable I.
The following is the assembly code of the constructor of x:

??0X@@QAE@H@Z PROC                    ; X::X, COMDAT
; _this$ = ecx
; 5    :     X(int ii) {
    push    ebp
    mov    ebp, esp
    push    ecx; The pressure of stack ecx The purpose is to give this Pointer to the (x First address of object ) The reserved 4byte The space of 
    mov    DWORD PTR _this$[ebp], ecx; will this The pointer is stored in the space just reserved  ecx It's stored x The first address 
    mov    eax, DWORD PTR _this$[ebp]; will x To register eax
    mov    DWORD PTR [eax], OFFSET ??_7X@@6B@; will ??_7X@@6B@ Offset address ( namely vtable The first address ) In the x The first address of an object points to an internal store   This is the initialization vptr Pointer to the 
; 6    :         i = ii;
    mov    ecx, DWORD PTR _this$[ebp]; will x The first address to ecx
    mov    edx, DWORD PTR _ii$[ebp]; The parameter ii Value to register edx
    mov    DWORD PTR [ecx+4], edx; To register eax Is written to offset x The first address 4byte In memory, that is to give x Member variable of i The assignment 
; 7    :     }
    mov    eax, DWORD PTR _this$[ebp]; will x Object first address to register eax , as the return value. The constructor always returns the first address of the object 
    mov    esp, ebp
    pop    ebp
    ret    4
??0X@@QAE@H@Z ENDP

As you can see from the code, the compiler does implicitly insert code to initialize the VPTR pointer with the first address of the vtable, and the VPTR pointer is at the first address of the object.
How does the constructor initialize the VPTR pointer if the class is inherited?
The following is the c++ source code:

class X {
private:
    int i;
public:
    virtual void f() {}
};
class Y : public X {//Y inherits from X
private:
   int j;
};
int main() {
 Y y;
}

The following is the assembly code in the main function:

_main    PROC
; 16   : int main() {
    push    ebp
    mov    ebp, esp
    sub    esp, 12                    ;  For the object y The reserved 12 byte The space of  vptr Pointer to the 4byte  Parent class member variable 4byte  Subclass member variable 4byte
; 17   :  Y y;
    lea    ecx, DWORD PTR _y$[ebp]; Access to the object y The first address ( namely this Pointer to the ), Passed to the constructor as an implicit parameter 
    call    ??0Y@@QAE@XZ; call y Constructor of   although y The definition constructor is not shown, but because it contains virtual member functions, the compiler provides the default constructor 
; 18   : }
    xor    eax, eax
    mov    esp, ebp
    pop    ebp
    ret    0
_main    ENDP

Here is the subclass constructor assembly code:

??0Y@@QAE@XZ PROC                    ; Y::Y, COMDAT
; _this$ = ecx
    push    ebp
    mov    ebp, esp
    push    ecx;//The purpose of pressing ecx is to hold this pointer
    mov    DWORD PTR _this$[ebp], ecx; will this Pointer to the ( The first address of the object ) I'm going to leave some space here  ecx It holds the first address of the object 
    mov    ecx, DWORD PTR _this$[ebp]; Gives the object's first address to ecx  Passed to the parent constructor as an implicit parameter 
    call    ??0X@@QAE@XZ; Call the superclass constructor 
    mov    eax, DWORD PTR _this$[ebp]; will y To register eax
    mov    DWORD PTR [eax], OFFSET ??_7Y@@6B@; will y the vtable(??_7Y@@6B@) First address to y The first address of the object refers to memory   That is to initialize the subclass vptr Pointer to the 
    mov    eax, DWORD PTR _this$[ebp]; will y The first address to eax , as the return value. The constructor always returns the first address of the object 
    mov    esp, ebp
    pop    ebp
    ret    0
??0Y@@QAE@XZ ENDP

The following is the parent constructor assembly code:

??0X@@QAE@XZ PROC                    ; X::X, COMDAT
; _this$ = ecx
    push    ebp
    mov    ebp, esp
    push    ecx; The purpose of pressing is storage this Pointer to the ( The parent object object's first address ) The reserved space 
    mov    DWORD PTR _this$[ebp], ecx; Sets the first address of the parent object (ecx Kept in ) I'm going to leave some space here 
    mov    eax, DWORD PTR _this$[ebp]; Gives the first address of the parent object to the register eax
    mov    DWORD PTR [eax], OFFSET ??_7X@@6B@; will vtable(??_7X@@6B@  Unlike subclasses ) The memory at the first address assigned to the parent object   That initializes the parent object vptr Pointer to the 
    mov    eax, DWORD PTR _this$[ebp]; Passes the first address of the parent object to eax , as the return value. The constructor always returns the first address of the object 
    mov    esp, ebp
    pop    ebp
    ret    0
??0X@@QAE@XZ ENDP

As can be seen from the constructor assembly encoding of the subclass and parent classes above, the child object contains the parent object, and the parent object is constructed first when the child object is constructed (the child object constructor calls the parent object constructor first). Also, the first address of the parent object is the same as the first address of the child object (as can be seen by the value passed by ecx in the assembly code), so the parent object and the child object's VPTR pointer are in the same place. So, in the construction of the object, the VPTR pointer is initialized to the first vtable address of the parent object (in the parent constructor), and finally to the first vtable address of the child object (in the constructor of the child object). Therefore, when inheritance is involved, the value of the VPTR pointer is determined by the constructor that was last called.
The virtual function mechanism fails in constructor calls, that is, the virtual function is always called in the local version of the constructor (also in destructors)
C++ source code as follows:

class X {
private:
    int i;
public:
    virtual void f(int ii) {
        i = ii;
    }
    X() {
       f(1);
    }
};
class Y : public X {//Y inherits from X
private:
   int j;
public:
    virtual void f(int ii) {
        j = ii;
    }
    Y() {
        f(2);
    }
};
int main() {
 Y y;
}

The following is mainly about the assembly coding of constructors in parent class X and subclass Y:
Constructor assembly code of subclass Y:

??0Y@@QAE@XZ PROC                    ; Y::Y, COMDAT
; _this$ = ecx
; 20   :     Y() {
    push    ebp
    mov    ebp, esp
    push    ecx; The purpose of pressing is for storage this Pointer to the ( in ecx The first address of the child object is stored in the register ) The reserved space 
    mov    DWORD PTR _this$[ebp], ecx; Store the first address of the child object in the space just reserved 
    mov    ecx, DWORD PTR _this$[ebp]; Pass the subclass's first address as an implicit parameter to the parent object constructor ( The first address of the child object is the same as the first address of the parent object )
    call    ??0X@@QAE@XZ                ;  Invokes the superclass constructor 
    mov    eax, DWORD PTR _this$[ebp]; Passes the first address of the child object to the register eax
    mov    DWORD PTR [eax], OFFSET ??_7Y@@6B@; Will child object vtable The first address is stored in the memory pointed to by the first address of the child object, that is, the initialization of the child object vptr Pointer to the 
; 21   :         f(2);
    push    2; will 2 Push the stack and call the function as an argument f Here, the child object is calling its own function f
    mov    ecx, DWORD PTR _this$[ebp]; Passes the first address of the child object to ecx , passed as an implicit parameter to the member function f
    call    ?f@Y@@UAEXH@Z                ;  Call in the child object f function 
; 22   :     }
    mov    eax, DWORD PTR _this$[ebp]; Gives the first address of a child object to a register eax , as the return value. The constructor always returns the first address of the object 
    mov    esp, ebp
    pop    ebp
    ret    0
??0Y@@QAE@XZ ENDP                    ; Y::Y

Assembly code of parent class X constructor:

??0X@@QAE@XZ PROC                    ; X::X, COMDAT
; _this$ = ecx
; 8    :     X() {
    push    ebp
    mov    ebp, esp
    push    ecx; The purpose of the stack is to hold the space reserved for the first address of the parent object   The first address of the parent object is the same as the first address of the child object 
    mov    DWORD PTR _this$[ebp], ecx;ecx It contains the first address of the parent object, which is passed to the reserved space 
    mov    eax, DWORD PTR _this$[ebp]; Passes the first address of the parent object to eax
    mov    DWORD PTR [eax], OFFSET ??_7X@@6B@; Of the parent object vtable The first address writes to the memory that the first address of the parent object points to   That initializes the parent object vptr Pointer to the 
; 9    :        f(1);
    push    1; will 1 Push the stack and call the function as an argument f  The version of the parent object when invoked here 
    mov    ecx, DWORD PTR _this$[ebp]; Passes the first address of the parent object as an implicit parameter f
    call    ?f@X@@UAEXH@Z                ;  Call a function f
; 10   :     }
    mov    eax, DWORD PTR _this$[ebp]; Passes the first address of the parent object to eax As the return value. The constructor always returns the first address of the object 
    mov    esp, ebp
    pop    ebp
    ret    0
??0X@@QAE@XZ ENDP

As you can see from the sink code, there really is no virtual mechanism in the constructor, which only calls the local version of the function
The destructor
When the destructor is executed, it initializes the VPTR pointer to the first address of the virtual vtable of the current class. However, if the compiler provides a non-useless default destructor when destructing, there will be no initialization of the VPTR pointer:
C + + source code:

class X {
private:
    int i;
public:
    virtual void set(int ii) {
        i = ii;
    }
    ~X() {}
};
class Y : public X {
private:
    int i;
};
int main() {
    Y y;
}

Class Y destructor assembly code:

??1Y@@QAE@XZ PROC                    ; Y::~Y, COMDAT
; _this$ = ecx
    push    ebp
    mov    ebp, esp
    push    ecx; For passing in y Object header reserved space 
    mov    DWORD PTR _this$[ebp], ecx;ecx Kept in y The first address of the object is stored in the previous space 
    mov    ecx, DWORD PTR _this$[ebp]; will y First address of object (y Objects contain parent objects with the same first address ) To pass to ecx , pass the address class as an implicit parameter X Destructor of 
    call    ??1X@@QAE@XZ                ;  Call the class X Destructor of 
    mov    esp, ebp
    pop    ebp
    ret    0
??1Y@@QAE@XZ ENDP

As you can see from the assembly code, the compiler provides a non-useless default destructor for the y object to call the destructor of the parent class, but in the destructor of the y object, there is no operation to initialize the VPTR pointer of the y object to the virtual vtable of class y.
The following is the destructor assembly code of class X:

??1X@@QAE@XZ PROC                    ; X::~X, COMDAT
; _this$ = ecx
; 9    :     ~X() {}
    push    ebp
    mov    ebp, esp
    push    ecx; Reserve space for the first address of the parent object passed in 
    mov    DWORD PTR _this$[ebp], ecx; Store the first address of the parent object in the previous space 
    mov    eax, DWORD PTR _this$[ebp]; The first address of the parent object is given eax
    mov    DWORD PTR [eax], OFFSET ??_7X@@6B@; The parent class vtable First address to the memory at the first address of the parent object   That is to initialize the parent object vptr Pointer to the 
    mov    esp, ebp
    pop    ebp
    ret    0
??1X@@QAE@XZ ENDP

The destructor of the parent class has an operation to initialize VPTR.

Abstract base class
C++ source code as follows:

class X {
private:
    int i;
public:
    virtual void f() = 0;//Pure virtual function
    X() {
       i = 1;
    }
};
class Y : public X {//Y inherits from X
private:
   int j;
public:
    virtual void f() {
        j = 2;
    }
};
int main() {
 Y y;
}

Just look at the constructor code of superclass X and subclass Y:
Subclass Y constructor assembly code:

??0Y@@QAE@XZ PROC                    ; Y::Y, COMDAT
; _this$ = ecx
    push    ebp
    mov    ebp, esp
    push    ecx; Reserve space to hold the first address of a child object  
    mov    DWORD PTR _this$[ebp], ecx; will ecx( It holds the first address of the child object ) I'm going to put the value of 
    mov    ecx, DWORD PTR _this$[ebp]; Passes the first address of the child object to ecx , as an implicit parameter (this Pointer to the ) Calls the constructor of the parent object 
    call    ??0X@@QAE@XZ                ;  Calls the constructor of the parent object 
    mov    eax, DWORD PTR _this$[ebp]; Gives the first address of the child object to eax t
    mov    DWORD PTR [eax], OFFSET ??_7Y@@6B@; Will child object vtable The first address is stored in the memory pointed to by the first address of the child object, that is, the initialization of the child object vptr
    mov    eax, DWORD PTR _this$[ebp]; Gives the first address of the child object to eax , as the return value. The constructor always returns the first address of the object 
    mov    esp, ebp
    pop    ebp
    ret    0
??0Y@@QAE@XZ ENDP

Parent class X constructor assembly code:

??0X@@QAE@XZ PROC                    ; X::X, COMDAT
; _this$ = ecx
; 6    :     X() {
    push    ebp
    mov    ebp, esp
    push    ecx; The purpose of the stack is to store the first address of the parent object ( namely this Pointer to the ) The reserved space 
    mov    DWORD PTR _this$[ebp], ecx; Saves the first address of the parent object into the space above 
    mov    eax, DWORD PTR _this$[ebp]; Passes the first address of the parent object to eax
    mov    DWORD PTR [eax], OFFSET ??_7X@@6B@; Of the parent object vtable( Since the parent class is an abstract class, the vtable Incomplete, that is, there is no address for the pure virtual function in it, only a place is reserved for it ) The memory in which the first address of the parent object is stored   That initializes the parent object vptr Pointer to the 
; 7    :        i = 1;
    mov    ecx, DWORD PTR _this$[ebp]; Gives the first address of the parent object ecx
    mov    DWORD PTR [ecx+4], 1; will 1 Save to the first address of the offset parent object 4byte Is the member variable given to the parent object i The assignment 
; 8    :     }
    mov    eax, DWORD PTR _this$[ebp]; The first address of the parent object eax  As the return value. The constructor always returns the first address of the object 
    mov    esp, ebp
    pop    ebp
    ret    0
??0X@@QAE@XZ ENDP

As you can see from the pool code, the constructor of the parent class is still called during subclass construction, even though the parent class is an abstract class. However, this is only to initialize the part of the child object that contains the parent object. If you want to instantiate an object directly from the parent class, the compiler will report an error. This is because the vtable of the parent class is incomplete, and the compiler cannot safely create an abstract class object. In the construction of the child object, although VPTR temporarily points to the vtable of the parent class in the construction of the child object, VPTR finally points to the vtable of the child class when the construction of the child object is completed. The subclass's vtable is a complete one, so the compiler allows it.
The polymorphic late binding mechanism works only when virtual functions are called with addresses or references, and if virtual functions are called directly from the object itself, there is no late binding, but a direct call.
C + + source code:

class X {
private:
    int i;
public:
    virtual void f() {
        i = 1;
    }
};
class Y : public X {//Y inherits from X
private:
   int j;
public:
    virtual void f() {
        j = 2;
    }
};
int main() {
 Y y;//Create objects on the stack
 Y* yp = new Y;//Create objects on the heap
 y.f();//Called directly with an object
 yp->f();//Call indirectly with a pointer
}

The same code at the page code block index 16
Mainly to use the object to directly call the function f and call the function f with the pointer assembly code:
Using the object to directly call the assembly code of function f:

; 25   :  y.f();
    lea    ecx, DWORD PTR _y$[ebp]; Objects that will be created on the stack y The first address of ecx , passed to as an implicit parameter f
    call    ?f@Y@@UAEXXZ                ;  Called with an absolute address f

Using pointer to indirectly call the assembly code of function f:

; 26   :  yp->f();
    mov    ecx, DWORD PTR _yp$[ebp]; will yp Pointer to the heap object's first address to ecx
    mov    edx, DWORD PTR [ecx]; Gives what the first address of the object created on the heap points to edx  the vptr Pointer pointing vtable The first address to edx
    mov    ecx, DWORD PTR _yp$[ebp]; will yp Pointer to the heap object's first address to ecx  Passed as an implicit parameter to the function to be called f
    mov    eax, DWORD PTR [edx];edx Deposit is vtable The first address, let's take it here vtable The first address of the content to eax  The function f The address to eax
    call    eax; call eax

It can be seen from the pool code that vtable is not accessed at all when the object is directly called, and vtable is only accessed when the pointer is called, forming a late binding. Because the compiler already knows the exact type of object when it is called directly from an object, a build bundle is used when these virtual functions are called for efficiency.
Inheritance and vtable
When a subclass inherits a parent class, the compiler recreates a vtable for the subclass, and the location of the virtual function in the parent class vatelbe maps exactly to the same location in the subclass vtable.
The following is the c++ source code:

class X {
private:
    int i;
public:
    virtual void a() {
        i = 1;
    }
    virtual void b() {
        i = 2;
    }
};
class Y : public X {
private:
    int i;
public:
    virtual void c() {//The newly defined virtual function
        i = 3;
    }
    void b() {//Overrides the virtual function in the superclass
        i = 4;
    }
};
int main() {
    X* xp = new X;
    X* yp = new Y;
    xp->a();
    xp->b();
    yp->a();
    yp->b();
    //Yp -> (c); The compiler reported an error
}

As you can see, the compiler reports an error when the virtual function c in the subclass is called with a yp pointer. This is because although the yp pointer points to a subclass Y of the time type, the compiler only targets the base class when compiling, and the base class only has the virtual functions a and b, so it is not allowed to call the virtual function c in the subclass, since the time type is converted up to the base class X type.
The following is only the assembly code for calling virtual functions:

; 28   :     xp->a();
    mov    edx, DWORD PTR _xp$[ebp]; will xp The first address of the heap object is given edx
    mov    eax, DWORD PTR [edx]; Gives the contents of the first address of the heap object eax , that is, vptr Point to the vtable The first address to eax
    mov    ecx, DWORD PTR _xp$[ebp]; will xp The first address of the heap object is given ecx , passed as an implicit parameter to the virtual member function to be called 
    mov    edx, DWORD PTR [eax]; will vtable The contents of the first address are given edx , is the virtual function a The address to edx( Here, virtual functions a Is located in the parent class X the vtable The first address )
    call    edx; Call the virtual member function a
; 29   :     xp->b();
    mov    eax, DWORD PTR _xp$[ebp]; will xp The first address of a heap object eax
    mov    edx, DWORD PTR [eax]; Gives the contents of the heap object's first address edx , that is, vptr Point to the vtable The first address to edx
    mov    ecx, DWORD PTR _xp$[ebp]; will xp The first address of a heap object ecx
    mov    eax, DWORD PTR [edx+4]; The offset vtable The first address 4byte The memory content is given eax , is the virtual function b The address to eax( Here, virtual functions b Is located in the offset parent class X the vtable The first address 4byte place )
    call    eax; Call the virtual member function b
; 30   :     yp->a();
    mov    ecx, DWORD PTR _yp$[ebp]; will yp The first address of the heap object to be pointed to ecx
    mov    edx, DWORD PTR [ecx]; Gives the contents of the heap object's first address edx , subclass vptr Point to the vtable The first address to edx
    mov    ecx, DWORD PTR _yp$[ebp]; will yp The first address of the heap object is given ecx , passed as an implicit parameter to the virtual member function a
    mov    eax, DWORD PTR [edx]; The subclass vtable The first address of the content to eax , is the virtual function a The address to eax( Here, virtual functions a Is also in the subclass Y the vtable The first address )
    call    eax; Call the virtual member function a
; 31   :     yp->b();
    mov    ecx, DWORD PTR _yp$[ebp]; will yp The first address of the heap object to be pointed to ecx
    mov    edx, DWORD PTR [ecx]; Gives the contents of the heap object's first address edx , subclass vptr Point to the vtable The first address to edx
    mov    ecx, DWORD PTR _yp$[ebp]; will yp The first address of the heap object is given ecx , passed as an implicit parameter to the virtual member function b
    mov    eax, DWORD PTR [edx+4]; Will offset subclass vtable The first address 4byte The contents of the memory given eax , is the virtual function b The address to eax( Here, virtual functions b Is also in the offset subclass Y the vtable The first address 4byte place )
    call    eax; Call the virtual member function b
; 32   :     //yp->c();

As can be seen from the assembly code, the position of a and b virtual functions in subclass vtable and superclass table is the same (as can be seen from their offsets relative to their vtable). This ensures that the compiler can always call virtual functions with the same offset, regardless of the actual type of the object. If do not do so, that is to say, virtual functions a, b in the vtable subclass Y location and location in the parent class X vtable, due to the upward transition, the compiler work only for the parent class, which is the virtual functions a, b's call will only according to the parent class X vtable to determine the offset, so will go wrong when I was in the actual operation, the actual child objects don't call the right function, polymorphism of failure.
In the above example, if we turn yp into the actual type and call c, we will see that the offset generated by the compiler is 8byte. The assembly code is as follows:

; 32   :     yp->c();
    mov    ecx, DWORD PTR _yp$[ebp]; will yp The first address of the heap object to be pointed to ecx
    mov    edx, DWORD PTR [ecx]; Gives the contents of the heap object's first address edx , subclass vptr Point to the vtable The first address to edx
    mov    ecx, DWORD PTR _yp$[ebp]; will yp The first address of the heap object is given ecx , passed as an implicit parameter to the virtual member function c
    mov    eax, DWORD PTR [edx+8]; Will offset subclass vtable The first address 8byte The contents of the memory given eax , is the virtual function c The address to eax( Here, virtual functions b Is also in the offset subclass Y the vtable The first address 8byte place )
    call    eax; Call the virtual member function c

Object section
If you pass a value instead of an address or a reference, an object slice occurs, in which the original part of the derived class object is removed, leaving only the part of the base class.
The following is the c++ source code:

class X {
private:
    int i;
public:
    virtual void a() {
        i = 1;
    }
    virtual void b() {
        i = 2;
    }
};
class Y : public X {
private:
    int i;
public:
    virtual void c() {//The newly defined virtual function
        i = 3;
    }
    void b() {//Overrides the virtual function in the superclass
        i = 4;
    }
};
void f(X x) {//Use the form of transfer value to do up conversion
    x.b();
}
int main() {
    Y y;
    f(y);
}

The following is the assembly code of the main function:

; 28   : int main() {
    push    ebp
    mov    ebp, esp
    sub    esp, 16                    ;  For the object y The reserved 16byte The space of 
; 29   :     Y y;
    lea    ecx, DWORD PTR _y$[ebp]; will y The first address of ecx Is passed to the implicit parameter y Constructor of 
    call    ??0Y@@QAE@XZ; call y Constructor of 
; 30   :     f(y);
    sub    esp, 8;//Since the object passes a value, it will be copied to produce a temporary object. Here we reserve 8 bytes of space for the temporary object (size of class X).
    mov    ecx, esp;//Gives the first address of the temporary object to ecx, passing it as an implicit parameter to the copy function
    lea    eax, DWORD PTR _y$[ebp]; The object y The first address of eax , as an argument to the copy function 
    push    eax; Push the stack, pass the parameters 
    call    ??0X@@QAE@ABV0@@Z; Call the class X Copy function of 
    call    ?f@@YAXVX@@@Z                ;  Call a function f
    add    esp, 8; Free the temporary object that was just occupied 8byte space 
; 31   : }
    xor    eax, eax
    mov    esp, ebp
    pop    ebp
    ret    0

As you can see from assembly, the size of the temporary object is the size of the parent class X, and the copy function called is also the copy function of the parent class X.
The following is the copy function assembly code of the parent class X:

??0X@@QAE@ABV0@@Z PROC                    ; X::X, COMDAT
; _this$ = ecx
    push    ebp
    mov    ebp, esp
    push    ecx; A stack reserved for the first address of the stored object 4byte space 
    mov    DWORD PTR _this$[ebp], ecx;ecx Save the first address of the temporary object in the reserved space 
    mov    eax, DWORD PTR _this$[ebp]; Gives the first address of the temporary object ecx
    mov    DWORD PTR [eax], OFFSET ??_7X@@6B@; The class X the vtable The first address is stored in the memory pointed to by the first address of the temporary object   That initializes the temporary object vptr Pointer to the 
    mov    ecx, DWORD PTR _this$[ebp]; Gives the first address of the temporary object ecx
    mov    edx, DWORD PTR ___that$[ebp]; will y The first address of edx
    mov    eax, DWORD PTR [edx+4]; The offset y The first address 4byte The memory content is given edx , that is, y Contains a member variable in the parent object i The value of the to edx
    mov    DWORD PTR [ecx+4], eax; will eax To the first address of the offset temporary object 4byte In memory, about to eax To the member variable of the temporary object i
    mov    eax, DWORD PTR _this$[ebp]; Gives the first address of the temporary object eax , as the return value. The constructor always returns the first address of the object 
    mov    esp, ebp
    pop    ebp
    ret    4

From the copy function, it can be seen that the temporary object only copies the contained part of the parent object of y (y is sliced), and the VPTR pointer of the temporary object is also initialized to the first address of vtable of class X.
The following is the assembly code of the function f:

; 24   : void f(X x) {
    push    ebp
    mov    ebp, esp
; 25   :     x.b();
    lea    ecx, DWORD PTR _x$[ebp]; The parameter x The first address of ecx , passed as an implicit parameter to the member function b
    call    ?b@X@@UAEXXZ                ;  call x Member function in b  This is called directly with the object, so there is no access vtable

The member function in class X is called, and the virtual table is not accessed
The following is the assembly code of the virtual member function b in class X:

?b@X@@UAEXXZ PROC                    ; X::b, COMDAT
; _this$ = ecx
; 8    :     virtual void b() {
    push    ebp
    mov    ebp, esp
    push    ecx; Reserved for the first address of the save object 4byte space 
    mov    DWORD PTR _this$[ebp], ecx;ecx The intermediate keeps the object x The first address of the 
; 9    :         i = 2;
    mov    eax, DWORD PTR _this$[ebp]; will x The first address to eax
    mov    DWORD PTR [eax+4], 2; will 2 To offset x The first address 4byte Place, 2 Assigned to x Member variable of i
; 10   :     }
    mov    esp, ebp
    pop    ebp
    ret    0
?b@X@@UAEXXZ ENDP


Related articles: