C++多态汇编分析

一:说明

        C++ 中由虚函数所引起的多态总让人有一种神秘感,本文通过对汇编代码的分析使整个调用过程一目了然,但前提是读者对C++想本身有所了解,且对虚函数的调用应该有一个清晰的概念,另外读者还应有一定的汇编基础。

        文章结构:

              一:说明

              二:C++  源码

              三:分析

                  3.1:从内存中得到分析所用的一些有用的值

                  3.2:各类的成员函数(大家只需将函数名及其首地址对应起来即可)

                  3.3 函数调用分析

        建议大家先搞清楚源码,最好在自己的机器上也调试一下,然后根据程序的执行流程再去看分析。

 

      分析环境及工具:

           VC++.NET 7.1 Realease 版本;禁止优化

二:C++  源码

 

#include "stdio.h"

#include <string.h>

 

class CBase1

{

public:

    CBase1(){};

    ~CBase1(){};

    virtual void virfun1();

    virtual void virfun2();

    virtual void base1();

    int num;

    int b1num;

};

 

class CBase2

{

public:

    int num;

    CBase2(){};

    ~CBase2(){};

    virtual void virfun1();

    virtual void virfun2();

    virtual void base2();

};

 

class CSub1 : public CBase1

{

public:

    CSub1(){};

    ~CSub1(){};

    virtual void virfun1( );

};

 

class CSub2 : public  CBase1, public CBase2

{

public:

    CSub2(){};

    ~CSub2(){};

    virtual void virfun1( );

    virtual void virfun3( );

    int s2num;

};

 

class CSub3 : public CSub2

{

public:

    CSub3(){};

    ~CSub3(){};

    virtual void base2();

    virtual void virfun2( );

    virtual void virfun4( );

};

 

//------------------------------------------------------------------------------

void CBase1::virfun1()

{

    printf("run in CBase1::virfun1( ) /n");

}

 

void CBase1::virfun2()

{

    printf("run in CBase1::virfun2( ) /n");

}

 

void CBase1::base1()

{

    printf("run in CBase1::base1( ) /n");

}

 

//------------------------------------------------------------------------------

void CBase2::virfun1()

{

    printf("run in CBase2::virfun1( ) /n");

}

void CBase2::virfun2()

{

    printf("run in CBase2::virfun2( ) /n");

}

 

void CBase2::base2()

{

    printf("run in CBase2::base2( ) /n");

}

//------------------------------------------------------------------------------

void CSub1::virfun1()

{

    printf("run in CSub1::virfun1( ) /n");

}

 

void CSub2::virfun1()

{

    printf("run in CSub2::virfun1( ) /n");

}

void CSub2::virfun3()

{

    printf("run in CSub2::virfun3( ) /n");

}

 

void CSub3::virfun2()

{

    printf("run in CSub3::virfun2( ) /n");

}

void CSub3::virfun4()

{

    printf("run in CSub3::virfun4( ) /n");

}

void CSub3::base2()

{

    printf("run in CSub3::base2( ) /n");

}

 

 

CBase1 base1;

CBase2 base2;

CSub1  sub1;

CSub2  sub2;

CSub3  sub3;

 

 

CBase1  *pbase1 = 0;

CBase2  *pbase2 = 0;

CSub1  *psub1 = 0;

CSub2  *psub2 = 0;

CSub3  *psub3 = 0;

 

 

void test1()

{

    base1.virfun1();

 

    base1.num = 1;

 

    pbase1->virfun1();

 

    pbase1->num = 1;

}

 

void test2()

{

    CBase1 &tembase1 = sub1;

 

    CBase1 *ptembase1 = &sub1;

 

    sub1.virfun1();

 

    psub1->virfun1();

 

    psub1->virfun2();

 

    tembase1.virfun1();

 

    ptembase1->virfun1();

 

    sub1.virfun2();

 

    psub1->virfun2();

 

    tembase1.virfun2();

 

    ptembase1->virfun2();

 

   

    sub1.num = 1;

 

    psub1->num = 2;

   

}

 

void test3()

{

    CBase1 &tembase1 = sub2; // <<===>> CBase1 *ptembase1 = &sub2;

 

    CBase2 &tembase2 = sub2; // <<===>> CBase2 *ptembase2 = &sub2;

 

    //sub2.virfun3();

    //sub2.virfun2();    // <<===>> psub2->virfun2();

    //error C2385: 对virfun2(在CSub2中)的访问不明确

 

    psub2->virfun1();

 

    psub2->s2num = 2;

 

    tembase1.num = 3;

 

    tembase2.num = 4;

 

    psub2->virfun3();

 

    psub2->base1();

 

    psub2->base2();

 

    tembase1.virfun1();      // <<===>> ptembase1->virfun1();

 

    tembase2.virfun1();      // <<===>> ptembase2->virfun1();

   

    tembase1.virfun2();      // <<===>> ptembase1->virfun2();

 

    tembase2.virfun2();      // <<===>> ptembase2->virfun2();

}

 

void test4()

{

    CBase1 &tembase1 = sub3; // <<===>> CBase1 *ptembase1 = &sub2;

 

    CBase2 &tembase2 = sub3; // <<===>> CBase2 *ptembase2 = &sub2;

 

    CSub2 &temsub2 = sub3;

 

    psub3->virfun4();

 

    psub3->virfun2();

 

    tembase1.virfun2();

 

    tembase2.virfun2();

 

    tembase2.base2();

 

    temsub2.base2();

}

 

int main( void )

{

 

    pbase1 = &base1;

    pbase2 = &base2;

    psub1 = &sub1;

    psub2 = &sub2;

    psub3 = &sub3;

    printf("base1:%lX    base2:%lX  /nsub1:%lX       sub2:%lX      sub3:%lX/n",

       (unsigned int)pbase1, (unsigned int)pbase2, (unsigned int)psub1, (int)psub2, (int)psub3);

    printf("sizeof(...) in byte/nbase1:%lu    base2:%lu  /nsub1:%lu       sub2:%lu       sub3:%lu/n",

       sizeof(base1), sizeof(base2), sizeof(sub1), sizeof(sub2), sizeof(sub3) );

   

    test1();

    test2();

    test3();

    test4();

}

 

 

三:分析

 

3.1:从内存中得到分析所用的一些有用的值:

 

五个全局对象

    CBase1    base1;

    CBase2    base2;

    CSub1     sub1;

    CSub2     sub2;

    CSub3     sub3;

在内存中的初始分布如下:

0x0040A6F0  xx xx xx xx 14 83 40 00  sub2:40A6F4  size:24  

0x0040A6F8  00 00 00 00 00 00 00 00 

0x0040A700  08 83 40 00 00 00 00 00 

0x0040A708  00 00 00 00 f0 82 40 00  base2:40A70C size:8

0x0040A710  00 00 00 00 e4 82 40 00  base1:40A714 size:12

0x0040A718  00 00 00 00 00 00 00 00 

0x0040A720  fc 82 40 00 00 00 00 00  sub1:40A720 size:12

0x0040A728  00 00 00 00 30 83 40 00  sub3:40A72C size:24

0x0040A730  00 00 00 00 00 00 00 00 

0x0040A738  24 83 40 00 00 00 00 00 

0x0040A740  00 00 00 00

 

0x0040A6F0  xxxxxxxx 00408314  sub2:40A6F4  size:24

0x0040A6F8  00000000 00000000 

0x0040A700  00408308 00000000 

0x0040A708  00000000 004082f0  base2:40A70C size:8

0x0040A710  00000000 004082e4  base1:40A714 size:12

0x0040A718  00000000 00000000 

0x0040A720  004082fc 00000000  sub1:40A720 size:12

0x0040A728  00000000 00408330  sub3:40A72C size:24

0x0040A730  00000000 00000000 

0x0040A738  00408324 00000000 

0x0040A740  00000000

 

五个类的虚表在内存中的存储情况如下:

 

CBase1:

0x004082E4  00401000  --> CBase1::virfun1()

0x004082E8  00401020  --> CBase1::virfun2()

0x004082EC  00401040  --> CBase1::base1()

 

CBase2:

0x004082F0  00401060  --> CBase2::virfun1()

0x004082F4  00401080  --> CBase2::virfun2()

0x004082F8  004010a0  --> CBase2::base2()

 

CSub1:

0x004082FC  004010c0  --> CSub1::virfun1()

0x00408300  00401020  --> CBase1::virfun2()

0x00408304  00401040  --> CBase1::base1()

 

CSub2 偏移为 0CH 处虚表指针所指向的内容:

0x00408308  00401540  --> CSub2::virfun1`adjustor{12}

0x0040830C  00401080  --> CBase2::virfun2()

0x00408310  004010a0  --> CBase2::base2()

 

CSub2 偏移为 0 处虚表指针所指向的内容:

0x00408314  004010e0  --> CSub2::virfun1()

0x00408318  00401020  --> CBase1::virfun2()

0x0040831C  00401040  --> CBase1::base1()

0x00408320  00401100  --> CSub2::virfun3()

 

CSub3 偏移为 0CH 处虚表指针所指向的内容:

0x00408324  00401540  --> CSub2::virfun1`adjustor{12}

0x00408328  00401580  --> CSub3::virfun2`adjustor{12}

0x0040832C  00401160  --> CSub3::base2()

 

CSub3 偏移为 0 处虚表指针所指向的内容:

0x00408330  004010e0  --> CSub2::virfun1()

0x00408334  00401120  --> CSub3::virfun2()

0x00408338  00401040  --> CBase1::base1()

0x0040833C  00401100  --> CSub2::virfun3()

0x00408340  00401140  --> CBase1::base1()

0x00408344  00000000 

0x00408348  ffffffff

 

 

两个adjustor:

[thunk]:CSub2::virfun1`adjustor{12}':

00401540 83 E9 0C         sub         ecx,0Ch

00401543 E9 98 FB FF FF   jmp         CSub2::virfun1 (4010E0h)

 

[thunk]:CSub3::virfun2`adjustor{12}':

00401580 83 E9 0C         sub         ecx,0Ch

00401583 E9 98 FB FF FF   jmp         CSub3::virfun2 (401120h)

 

3.2:各类的成员函数(大家只需将函数名及其首地址对应起来即可)

 

    55: //------------------------------------------------------------------------------

    56: void CBase1::virfun1()

    57: {

00401000 55               push        ebp 

00401001 8B EC            mov         ebp,esp

00401003 51               push        ecx 

00401004 89 4D FC         mov         dword ptr [ebp-4],ecx

    58:    printf("run in CBase1::virfun1( ) /n");

00401007 68 10 81 40 00   push        offset KERNEL32_NULL_THUNK_DATA+30h (408110h)

0040100C E8 65 06 00 00   call        printf (401676h)

00401011 83 C4 04         add         esp,4

    59: }

00401014 8B E5            mov         esp,ebp

00401016 5D               pop         ebp 

00401017 C3               ret             

 

    60:

    61: void CBase1::virfun2()

    62: {

00401020 55               push        ebp 

00401021 8B EC            mov         ebp,esp

00401023 51               push        ecx 

00401024 89 4D FC         mov         dword ptr [ebp-4],ecx

    63:    printf("run in CBase1::virfun2( ) /n");

00401027 68 2C 81 40 00   push        offset KERNEL32_NULL_THUNK_DATA+4Ch (40812Ch)

0040102C E8 45 06 00 00   call        printf (401676h)

00401031 83 C4 04         add         esp,4

    64: }

00401034 8B E5            mov         esp,ebp

00401036 5D               pop         ebp 

00401037 C3               ret             

 

    65:

    66: void CBase1::base1()

    67: {

00401040 55               push        ebp 

00401041 8B EC            mov         ebp,esp

00401043 51               push        ecx 

00401044 89 4D FC         mov         dword ptr [ebp-4],ecx

    68:    printf("run in CBase1::base1( ) /n");

00401047 68 48 81 40 00   push        offset KERNEL32_NULL_THUNK_DATA+68h (408148h)

0040104C E8 25 06 00 00   call        printf (401676h)

00401051 83 C4 04         add         esp,4

    69: }

00401054 8B E5            mov         esp,ebp

00401056 5D               pop         ebp 

00401057 C3               ret             

 

    70:

    71: //------------------------------------------------------------------------------

    72: void CBase2::virfun1()

    73: {

00401060 55               push        ebp 

00401061 8B EC            mov         ebp,esp

00401063 51               push        ecx 

00401064 89 4D FC         mov         dword ptr [ebp-4],ecx

    74:    printf("run in CBase2::virfun1( ) /n");

00401067 68 64 81 40 00   push        offset KERNEL32_NULL_THUNK_DATA+84h (408164h)

0040106C E8 05 06 00 00   call        printf (401676h)

00401071 83 C4 04         add         esp,4

    75: }

00401074 8B E5            mov         esp,ebp

00401076 5D               pop         ebp 

00401077 C3               ret             

 

    76: void CBase2::virfun2()

    77: {

00401080 55               push        ebp 

00401081 8B EC            mov         ebp,esp

00401083 51               push        ecx 

00401084 89 4D FC         mov         dword ptr [ebp-4],ecx

    78:    printf("run in CBase2::virfun2( ) /n");

00401087 68 80 81 40 00   push        offset KERNEL32_NULL_THUNK_DATA+0A0h (408180h)

0040108C E8 E5 05 00 00   call        printf (401676h)

00401091 83 C4 04         add         esp,4

    79: }

00401094 8B E5            mov         esp,ebp

00401096 5D               pop         ebp 

00401097 C3               ret             

 

    80:

    81: void CBase2::base2()

    82: {

004010A0 55               push        ebp 

004010A1 8B EC            mov         ebp,esp

004010A3 51               push        ecx 

004010A4 89 4D FC         mov         dword ptr [ebp-4],ecx

    83:    printf("run in CBase2::base2( ) /n");

004010A7 68 9C 81 40 00   push        offset KERNEL32_NULL_THUNK_DATA+0BCh (40819Ch)

004010AC E8 C5 05 00 00   call        printf (401676h)

004010B1 83 C4 04         add         esp,4

    84: }

004010B4 8B E5            mov         esp,ebp

004010B6 5D               pop         ebp 

004010B7 C3               ret             

 

    85: //------------------------------------------------------------------------------

    86: void CSub1::virfun1()

    87: {

004010C0 55               push        ebp 

004010C1 8B EC            mov         ebp,esp

004010C3 51               push        ecx 

004010C4 89 4D FC         mov         dword ptr [ebp-4],ecx

    88:    printf("run in CSub1::virfun1( ) /n");

004010C7 68 B8 81 40 00   push        offset KERNEL32_NULL_THUNK_DATA+0D8h (4081B8h)

004010CC E8 A5 05 00 00   call        printf (401676h)

004010D1 83 C4 04         add         esp,4

    89: }

004010D4 8B E5            mov         esp,ebp

004010D6 5D               pop         ebp 

004010D7 C3               ret             

 

    90:

    91: void CSub2::virfun1()

    92: {

004010E0 55               push        ebp 

004010E1 8B EC            mov         ebp,esp

004010E3 51               push        ecx 

004010E4 89 4D FC         mov         dword ptr [ebp-4],ecx

    93:    printf("run in CSub2::virfun1( ) /n");

004010E7 68 D4 81 40 00   push        offset KERNEL32_NULL_THUNK_DATA+0F4h (4081D4h)

004010EC E8 85 05 00 00   call        printf (401676h)

004010F1 83 C4 04         add         esp,4

    94: }

004010F4 8B E5            mov         esp,ebp

004010F6 5D               pop         ebp 

004010F7 C3               ret             

 

    95: void CSub2::virfun3()

    96: {

00401100 55               push        ebp 

00401101 8B EC            mov         ebp,esp

00401103 51               push        ecx 

00401104 89 4D FC         mov         dword ptr [ebp-4],ecx

    97:    printf("run in CSub2::virfun3( ) /n");

00401107 68 F0 81 40 00   push        offset KERNEL32_NULL_THUNK_DATA+110h (4081F0h)

0040110C E8 65 05 00 00   call        printf (401676h)

00401111 83 C4 04         add         esp,4

    98: }

00401114 8B E5            mov         esp,ebp

00401116 5D               pop         ebp 

00401117 C3               ret             

 

    99:

   100: void CSub3::virfun2()

   101: {

00401120 55               push        ebp 

00401121 8B EC            mov         ebp,esp

00401123 51               push        ecx 

00401124 89 4D FC         mov         dword ptr [ebp-4],ecx

   102:    printf("run in CSub3::virfun2( ) /n");

00401127 68 0C 82 40 00   push        offset KERNEL32_NULL_THUNK_DATA+12Ch (40820Ch)

0040112C E8 45 05 00 00   call        printf (401676h)

00401131 83 C4 04         add         esp,4

   103: }

00401134 8B E5            mov         esp,ebp

00401136 5D               pop         ebp 

00401137 C3               ret             

 

   104: void CSub3::virfun4()

   105: {

00401140 55               push        ebp 

00401141 8B EC            mov         ebp,esp

00401143 51               push        ecx 

00401144 89 4D FC         mov         dword ptr [ebp-4],ecx

   106:    printf("run in CSub3::virfun4( ) /n");

00401147 68 28 82 40 00   push        offset KERNEL32_NULL_THUNK_DATA+148h (408228h)

0040114C E8 25 05 00 00   call        printf (401676h)

00401151 83 C4 04         add         esp,4

   107: }

00401154 8B E5            mov         esp,ebp

00401156 5D               pop         ebp 

00401157 C3               ret             

 

   108: void CSub3::base2()

   109: {

00401160 55               push        ebp 

00401161 8B EC            mov         ebp,esp

00401163 51               push        ecx 

00401164 89 4D FC         mov         dword ptr [ebp-4],ecx

   110:    printf("run in CSub3::base2( ) /n");

00401167 68 44 82 40 00   push        offset KERNEL32_NULL_THUNK_DATA+164h (408244h)

0040116C E8 05 05 00 00   call        printf (401676h)

00401171 83 C4 04         add         esp,4

   111: }

00401174 8B E5            mov         esp,ebp

00401176 5D               pop         ebp 

00401177 C3               ret             

 

3.3 函数调用分析

 

   128: void test1()

   129: {

00401180 55               push        ebp 

00401181 8B EC            mov         ebp,esp

   130:    base1.virfun1();

00401183 B9 14 A7 40 00   mov         ecx,offset base1 (40A714h)

00401188 E8 73 FE FF FF   call        CBase1::virfun1 (401000h)

       this指针存至  ECX 寄存器,对象直接调用成员函数时直接用 CALL 指令;

   131:

   132:    base1.num = 1;

0040118D C7 05 18 A7 40 00 01 00 00 00 mov         dword ptr [base1+4 (40A718h)],1

            对象直接访问简单成员变量:dword ptr [base1]  为 base1 的虚表指针,

                                      dword ptr [base1+4]为  base1 的第一个成员变量,即要访问的  base1.num

   133:

   134:    pbase1->virfun1();

00401197 A1 E0 A6 40 00   mov         eax,dword ptr [pbase1 (40A6E0h)]

            对象指针 pbase1 的内容即为所指对象的首地址,将 base1 的首地址存至 EAX 寄存器

0040119C 8B 10            mov         edx,dword ptr [eax]

            取得对象的虚表指针并存至 EDX 寄存器

0040119E 8B 0D E0 A6 40 00 mov         ecx,dword ptr [pbase1 (40A6E0h)]

            this指针存至  ECX 寄存器

004011A4 FF 12            call        dword ptr [edx]

            由虚表指针取得第一个虚函数,即所调用的virfun1()

   135:

   136:    pbase1->num = 1;

004011A6 A1 E0 A6 40 00   mov         eax,dword ptr [pbase1 (40A6E0h)]

            将 base1 的首地址存至 EAX 寄存器

004011AB C7 40 04 01 00 00 00 mov         dword ptr [eax+4],1

            访问对象的成员变量:首地址+4为成员变量的开始

   137: }

004011B2 5D               pop         ebp 

004011B3 C3               ret             

 

   138:

   139: void test2()

   140: {

004011C0 55               push        ebp 

004011C1 8B EC            mov         ebp,esp

004011C3 83 EC 08         sub         esp,8

            8字节的临时变量

   141:    CBase1 &tembase1 = sub1;

004011C6 C7 45 FC 20 A7 40 00 mov         dword ptr [tembase1],offset sub1 (40A720h)

   142:

   143:    CBase1 *ptembase1 = &sub1;

004011CD C7 45 F8 20 A7 40 00 mov         dword ptr [ptembase1],offset sub1 (40A720h)

            C++中的引用在存储处理上等价于指针,在32位机器上,每个引用各占4个字节,存储所引对象的首地址

              CSub1  的虚表如下:

            CSub1:

                  0x004082FC  004010c0  --> CSub1::virfun1()

                  0x00408300  00401020  --> CBase1::virfun2()

                  0x00408304  00401040  --> CBase1::base1()

   144:

   145:    sub1.virfun1();

004011D4 B9 20 A7 40 00   mov         ecx,offset sub1 (40A720h)

004011D9 E8 E2 FE FF FF   call        CSub1::virfun1 (4010C0h)

            对象直接调用函数,注意这里调用的是CSub1::virfun1(),而不是  CBase1::virfun1()

   146:

   147:    psub1->virfun1();

004011DE A1 E8 A6 40 00   mov         eax,dword ptr [psub1 (40A6E8h)]

004011E3 8B 10            mov         edx,dword ptr [eax]

004011E5 8B 0D E8 A6 40 00 mov         ecx,dword ptr [psub1 (40A6E8h)]

004011EB FF 12            call        dword ptr [edx]

            指针调用函数,分析参 test1()

   148:

   149:    psub1->virfun2();

004011ED A1 E8 A6 40 00   mov         eax,dword ptr [psub1 (40A6E8h)]

004011F2 8B 10            mov         edx,dword ptr [eax]

004011F4 8B 0D E8 A6 40 00 mov         ecx,dword ptr [psub1 (40A6E8h)]

004011FA FF 52 04         call        dword ptr [edx+4]

            指针调用函数,分析参 test1()

   150:

   151:    tembase1.virfun1();

004011FD 8B 45 FC         mov         eax,dword ptr [tembase1]

            将 sub1 的首地址存至 EAX 寄存器

00401200 8B 10            mov         edx,dword ptr [eax]

            将对象的虚表指针存至 EDX 寄存器

00401202 8B 4D FC         mov         ecx,dword ptr [tembase1]

            this指针存至  ECX 寄存器

00401205 FF 12            call        dword ptr [edx]

            引用调用函数,由生成的代码可知:C++中引用在本质上就是指针

   152:

   153:    ptembase1->virfun1();

00401207 8B 45 F8         mov         eax,dword ptr [ptembase1]

0040120A 8B 10            mov         edx,dword ptr [eax]

0040120C 8B 4D F8         mov         ecx,dword ptr [ptembase1]

0040120F FF 12            call        dword ptr [edx]

   154:

   155:    sub1.virfun2();

00401211 B9 20 A7 40 00   mov         ecx,offset sub1 (40A720h) this指针

00401216 E8 05 FE FF FF   call        CBase1::virfun2 (401020h)

   156:

   157:    psub1->virfun2();

0040121B A1 E8 A6 40 00   mov         eax,dword ptr [psub1 (40A6E8h)] 对象的首地址

00401220 8B 10            mov         edx,dword ptr [eax] 虚表指针

00401222 8B 0D E8 A6 40 00 mov         ecx,dword ptr [psub1 (40A6E8h)] this指针

00401228 FF 52 04         call        dword ptr [edx+4] 虚表中第二个函数

   158:

   159:    tembase1.virfun2();

0040122B 8B 45 FC         mov         eax,dword ptr [tembase1] 对象的首地址

0040122E 8B 10            mov         edx,dword ptr [eax] 虚表指针

00401230 8B 4D FC         mov         ecx,dword ptr [tembase1] this指针

00401233 FF 52 04         call        dword ptr [edx+4] 虚表中第二个函数

   160:

   161:    ptembase1->virfun2();

00401236 8B 45 F8         mov         eax,dword ptr [ptembase1] 对象的首地址

00401239 8B 10            mov         edx,dword ptr [eax] 虚表指针

0040123B 8B 4D F8         mov         ecx,dword ptr [ptembase1] this指针

0040123E FF 52 04         call        dword ptr [edx+4] 虚表中第二个函数

   162:

   163:   

   164:    sub1.num = 1;

00401241 C7 05 24 A7 40 00 01 00 00 00 mov         dword ptr [sub1+4 (40A724h)],1

   165:

   166:    psub1->num = 2;

0040124B A1 E8 A6 40 00   mov         eax,dword ptr [psub1 (40A6E8h)] this指针

00401250 C7 40 04 02 00 00 00 mov         dword ptr [eax+4],2

   167:   

   168: }

00401257 8B E5            mov         esp,ebp

00401259 5D               pop         ebp 

0040125A C3               ret             

 

   169:

   170: void test3()

   171: {

00401260 55               push        ebp 

00401261 8B EC            mov         ebp,esp

00401263 83 EC 0C         sub         esp,0Ch

            12字节的临时变量,其中8字节为我们声明的两个引用,另外4个字节编译器使用

   172:    CBase1 &tembase1 = sub2; // <<===>> CBase1 *ptembase1 = &sub2;

00401266 C7 45 FC F4 A6 40 00 mov         dword ptr [tembase1],offset sub2 (40A6F4h)

            类型为 CBase1 的引用 tembase1, 其内容为 sub2 的首地址

 

sub2 在内存中的初始存储情况:(从0x0040A6F4 开始,共24字节)

0x0040A6F0  xxxxxxxx 00408314

0x0040A6F8  00000000 00000000 

0x0040A700  00408308 00000000 

0x0040A708  00000000

可以看出:由于 sub2 有两个父类 CBase1 和 CBase2 (源程序中继承时这两个类的顺序对 sub2 各变量在内存中分布有很大影响,参下面分析)其中在偏移 0 处的虚表指针由 CBase1 继承而来,sub2 的非继承虚函数(CSub2 自己的虚函数)的地址会加到这个指针所指向的虚表中;在偏移 0Ch 处的虚表指针由 CBase2 继承而来,编译器会根据 CSub2 类中虚函数的实现情况修改虚表中的内容。

   173:

   174:    CBase2 &tembase2 = sub2; // <<===>> CBase2 *ptembase2 = &sub2;

0040126D B8 F4 A6 40 00   mov         eax,offset sub2 (40A6F4h)    取 sub2 的首地址

00401272 85 C0            test        eax,eax                     判断引用是否为0

00401274 74 09            je          test3+1Fh (40127Fh)         为 0 则跳转到(40127Fh) (这里不跳转)

00401276 C7 45 F4 00 A7 40 00 mov         dword ptr [ebp-0Ch],offset sub2+0Ch (40A700h)

            将对象 sub2 中偏移为 0CH 处的有效地址移到一临时变量中

0040127D EB 07            jmp         test3+26h (401286h)

0040127F C7 45 F4 00 00 00 00 mov         dword ptr [ebp-0Ch],0

            如果 sub2 的首地址 0,置临时变量也为 0

00401286 8B 4D F4         mov         ecx,dword ptr [ebp-0Ch]

00401289 89 4D F8         mov         dword ptr [tembase2],ecx

            将临时变量赋值给 tembase2,即 CBase2 类型的引用

由上分析可知,CBase2 类型的引用是将 sub2 中偏移为 0CH 处的有效地址作为所引用对象的首地址的,这和 sub2 各变量在内存中分布相一致

   175:

   176:    //sub2.virfun3();

   177:    //sub2.virfun2();    // <<===>> psub2->virfun2();

   178:    //error C2385: 对virfun2(在CSub2中)的访问不明确

            CSub2 没有实现自己的 virfun2(),而两个父类中都有名为 virfun2 的虚函数,所以 sub2.virfun2()这种调用对函数的访问不明确

   179:

   180:    psub2->virfun1();

0040128C 8B 15 EC A6 40 00 mov         edx,dword ptr [psub2 (40A6ECh)] 取对象的首地址

00401292 8B 02            mov         eax,dword ptr [edx] 取偏移为 0 的虚表指针

00401294 8B 0D EC A6 40 00 mov         ecx,dword ptr [psub2 (40A6ECh)] 取对象的首地址存到 ECX 中作为对象的this指针

0040129A FF 10            call        dword ptr [eax] 调用虚表中第一个虚函数

   181:

   182:    psub2->s2num = 2;

0040129C 8B 0D EC A6 40 00 mov         ecx,dword ptr [psub2 (40A6ECh)]

004012A2 C7 41 14 02 00 00 00 mov         dword ptr [ecx+14h],2

   183:

   184:    tembase1.num = 3;

004012A9 8B 55 FC         mov         edx,dword ptr [tembase1]

004012AC C7 42 04 03 00 00 00 mov         dword ptr [edx+4],3

   185:

   186:    tembase2.num = 4;

004012B3 8B 45 F8         mov         eax,dword ptr [tembase2]

004012B6 C7 40 04 04 00 00 00 mov         dword ptr [eax+4],4

            执行完上面三条赋值语句后 sub2 在内存中的样子:

以单字节为单位:

0x0040A6F0  xx xx xx xx 14 83 40 00 

0x0040A6F8  03 00 00 00 00 00 00 00 

0x0040A700  08 83 40 00 04 00 00 00 

0x0040A708  02 00 00 00

以四字节为单位:

0x0040A6F0  xxxxxxxx 00408314 

0x0040A6F8  00000003 00000000 

0x0040A700  00408308 00000004 

0x0040A708  00000002

由此可对 sub2 中各变量在内存中的分布有更进一步的认识

   187:

   188:    psub2->virfun3();

004012BD 8B 0D EC A6 40 00 mov         ecx,dword ptr [psub2 (40A6ECh)]

004012C3 8B 11            mov         edx,dword ptr [ecx]

004012C5 8B 0D EC A6 40 00 mov         ecx,dword ptr [psub2 (40A6ECh)]

004012CB FF 52 0C         call        dword ptr [edx+0Ch]

   189:

   190:    psub2->base1();

004012CE A1 EC A6 40 00   mov         eax,dword ptr [psub2 (40A6ECh)]

004012D3 8B 10            mov         edx,dword ptr [eax]

004012D5 8B 0D EC A6 40 00 mov         ecx,dword ptr [psub2 (40A6ECh)]

004012DB FF 52 08         call        dword ptr [edx+8]

   191:

   192:    psub2->base2();

004012DE 8B 0D EC A6 40 00 mov         ecx,dword ptr [psub2 (40A6ECh)] 取 sub2 首地址于 ECX 中

004012E4 83 C1 0C         add         ecx,0Ch 将 sub2 中偏移为 0CH 的有效地址作为调用 base2()时所用的this指针

004012E7 A1 EC A6 40 00   mov         eax,dword ptr [psub2 (40A6ECh)] 取 sub2 首地址

004012EC 8B 50 0C         mov         edx,dword ptr [eax+0Ch] 取偏移为 0CH 的虚表指针

004012EF FF 52 08         call        dword ptr [edx+8] 调用虚表中第三个虚函数

            注意,在调用 CBase1::base1() 和调用 CBase2::base2()函数时函数所使用的 this 指针,也就是 ECX 寄存器的内容是不一样的,请注意区别

   193:

            在 C++ 中,只有指针或引用支持 OO 程序设计中所需的多态性质。下面两个调用在代码上看似相同,执行结果也相同:都是调用 CSub2::virfun1(),但应该注意到引用 tembase1 与 tembase2 是不一样的:引用 tembase1 内容为 sub2 的首地址,引用 tembase2 内容为 sub2 中偏移为 0CH 处的地址。下面具体分析:

   194:    tembase1.virfun1();      // <<===>> ptembase1->virfun1();

004012F2 8B 45 FC         mov         eax,dword ptr [tembase1]

            sub2 的首地址移至 EAX

004012F5 8B 10            mov         edx,dword ptr [eax]

            sub2 中偏移为 0 的虚表指针移至 EDX

004012F7 8B 4D FC         mov         ecx,dword ptr [tembase1]

            sub2 的首地址移至 ECX 作为调用函数所用的 this 指针

004012FA FF 12            call        dword ptr [edx]

            调用虚表中第一个函数,即 CSub2::virfun1()

 

CSub2 偏移为 0 处虚表指针所指向的内容:

0x00408314  004010e0  --> CSub2::virfun1()

0x00408318  00401020  --> CBase1::virfun2()

0x0040831C  00401040  --> CBase1::base1()

0x00408320  00401100  --> CSub2::virfun3()

 

   195:

   196:    tembase2.virfun1();      // <<===>> ptembase2->virfun1();

004012FC 8B 45 F8         mov         eax,dword ptr [tembase2]

            sub2 中偏移为 0CH 处的地址移至 EAX

004012FF 8B 10            mov         edx,dword ptr [eax]

            sub2 中偏移为 0CH 的虚表指针移至 EDX

00401301 8B 4D F8         mov         ecx,dword ptr [tembase2]

            sub2 的偏移为 0CH 处的地址移至 ECX 作为调用函数所用的 this 指针吗?

00401304 FF 12            call        dword ptr [edx]

            调用偏移为 0CH 的虚表中第一个函数,但目前看函数所需的 this 指针不对呀:实际执行的是 CSub2::virfun1(),那么在执行前 ECX 的值应为 sub2 的首地址才对。让我们再看一下虚表中的内容吧:

 

CSub2 偏移为 0CH 处虚表指针所指向的内容:

0x00408308  00401540  --> CSub2::virfun1`adjustor{12}

0x0040830C  00401080  --> CBase2::virfun2()

0x00408310  004010a0  --> CBase2::base2()

这个虚表中第一个项不是 CSub2::virfun1() 的地址,而是一个所谓的 adjustor 的地址,让我们再看看这个 adjustor:

 

[thunk]:CSub2::virfun1`adjustor{12}':

00401540 83 E9 0C         sub         ecx,0Ch

00401543 E9 98 FB FF FF   jmp         CSub2::virfun1 (4010E0h)

到此,一切都清楚了:在  adjustor 中, ECX 的值终于改成了我们所认为的 sub2 的首地址,而且最终执行的是 CSub2::virfun1()。下面两个函数的调用和上面类似,只不过 CSub2 偏移为 0CH 处虚表指针所指向的虚表中第二项不是adjustor 的地址,而是 CBase2::virfun2() 的地址,那么在调用tembase2.virfun2()时 ECX 的值就是所要的:sub2 的偏移为 0CH 处的地址

   197:   

   198:    tembase1.virfun2();      // <<===>> ptembase1->virfun2();

00401306 8B 45 FC         mov         eax,dword ptr [tembase1]

00401309 8B 10            mov         edx,dword ptr [eax]

0040130B 8B 4D FC         mov         ecx,dword ptr [tembase1]

0040130E FF 52 04         call        dword ptr [edx+4]

   199:

   200:    tembase2.virfun2();      // <<===>> ptembase2->virfun2();

00401311 8B 45 F8         mov         eax,dword ptr [tembase2]

00401314 8B 10            mov         edx,dword ptr [eax]

00401316 8B 4D F8         mov         ecx,dword ptr [tembase2]

00401319 FF 52 04         call        dword ptr [edx+4]

   201: }

0040131C 8B E5            mov         esp,ebp

0040131E 5D               pop         ebp 

0040131F C3               ret             

   202:

 

test4()中各调用和上面分析无本质差别,有兴趣的朋友可自己分析一下。

 

   203: void test4()

   204: {

00401320 55               push        ebp 

00401321 8B EC            mov         ebp,esp

00401323 83 EC 10         sub         esp,10h

   205:    CBase1 &tembase1 = sub3; // <<===>> CBase1 *ptembase1 = &sub2;

00401326 C7 45 FC 2C A7 40 00 mov         dword ptr [tembase1],offset sub3 (40A72Ch)

   206:

   207:    CBase2 &tembase2 = sub3; // <<===>> CBase2 *ptembase2 = &sub2;

0040132D B8 2C A7 40 00   mov         eax,offset sub3 (40A72Ch)

00401332 85 C0            test        eax,eax

00401334 74 09            je          test4+1Fh (40133Fh)

00401336 C7 45 F0 38 A7 40 00 mov         dword ptr [ebp-10h],offset sub3+0Ch (40A738h)

0040133D EB 07            jmp         test4+26h (401346h)

0040133F C7 45 F0 00 00 00 00 mov         dword ptr [ebp-10h],0

00401346 8B 4D F0         mov         ecx,dword ptr [ebp-10h]

00401349 89 4D F8         mov         dword ptr [tembase2],ecx

   208:

   209:    CSub2 &temsub2 = sub3;

0040134C C7 45 F4 2C A7 40 00 mov         dword ptr [temsub2],offset sub3 (40A72Ch)

   210:

   211:    psub3->virfun4();

00401353 8B 15 F0 A6 40 00 mov         edx,dword ptr [psub3 (40A6F0h)]

00401359 8B 02            mov         eax,dword ptr [edx]

0040135B 8B 0D F0 A6 40 00 mov         ecx,dword ptr [psub3 (40A6F0h)]

00401361 FF 50 10         call        dword ptr [eax+10h]

   212:

   213:    psub3->virfun2();

00401364 8B 0D F0 A6 40 00 mov         ecx,dword ptr [psub3 (40A6F0h)]

0040136A 8B 11            mov         edx,dword ptr [ecx]

0040136C 8B 0D F0 A6 40 00 mov         ecx,dword ptr [psub3 (40A6F0h)]

00401372 FF 52 04         call        dword ptr [edx+4]

   214:

   215:    tembase1.virfun2();

00401375 8B 45 FC         mov         eax,dword ptr [tembase1]

00401378 8B 10            mov         edx,dword ptr [eax]

0040137A 8B 4D FC         mov         ecx,dword ptr [tembase1]

0040137D FF 52 04         call        dword ptr [edx+4]

   216:

   217:    tembase2.virfun2();

00401380 8B 45 F8         mov         eax,dword ptr [tembase2]

00401383 8B 10            mov         edx,dword ptr [eax]

00401385 8B 4D F8         mov         ecx,dword ptr [tembase2]

00401388 FF 52 04         call        dword ptr [edx+4]

   218:

   219:    tembase2.base2();

0040138B 8B 45 F8         mov         eax,dword ptr [tembase2]

0040138E 8B 10            mov         edx,dword ptr [eax]

00401390 8B 4D F8         mov         ecx,dword ptr [tembase2]

00401393 FF 52 08         call        dword ptr [edx+8]

   220:

   221:    temsub2.base2();

00401396 8B 4D F4         mov         ecx,dword ptr [temsub2]

00401399 83 C1 0C         add         ecx,0Ch

0040139C 8B 45 F4         mov         eax,dword ptr [temsub2]

0040139F 8B 50 0C         mov         edx,dword ptr [eax+0Ch]

004013A2 FF 52 08         call        dword ptr [edx+8]

   222: }

004013A5 8B E5            mov         esp,ebp

004013A7 5D               pop         ebp 

004013A8 C3               ret             

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值