} } }

    从汇编看c++中指向成员变量的指针(二)

    添加时间:2013-7-10 点击量:

    在从汇编看c++中指向成员变量的指针(一)中评论辩论的景象没有虚拟持续,下面来看看,当参加了虚拟持续的时辰,指向成员变量的指针有什么变更。


    下面是c++源码:



    #include <iostream>
    
    #include
    <cstdio>
    using namespace std;

    class Top {
    public:
    int _top;
    };
    class Left : public virtual Top {
    public:
    int _left;
    };

    class Right : public virtual Top {
    public:
    int _right;
    };

    class Bottom : public Left, public Right {
    public:
    int _bottom;

    };

    int main() {
    Bottom b;
    Bottom
    bp = &b;
    Top
    tp = bp;
    Left
    lp = bp;
    Right
    rp = bp;

    //虚基类Top中的成员变量指针
    int Top::tmp1 = &Top::_top;
    //Left中的成员变量指针
    int Left::lmp1 = &Left::_top;
    int Left::lmp2 = &Left::_left;
    //Right中的成员变量指针
    int Right::rmp1 = &Right::_top;
    int Right::rmp2 = &Right::_right;
    //Bottom中的成员变量指针
    int Bottom::bmp1 = &Bottom::_top;
    int Bottom::bmp2 = &Bottom::_left;
    int Bottom::bmp3 = &Bottom::_right;
    int Bottom::bmp4 = &Bottom::_bottom;

    //输出各成员变量指针的大小
    cout << 各成员变量指针的大小 << endl;
    cout
    << sizeof(tmp1) = << sizeof(tmp1) << endl;
    cout
    << sizeof(lmp1) = << sizeof(lmp1) << endl;
    cout
    << sizeof(lmp2) = << sizeof(lmp2) << endl;
    cout
    << sizeof(rmp1) = << sizeof(rmp1) << endl;
    cout
    << sizeof(rmp2) = << sizeof(rmp2) << endl;
    cout
    << sizeof(bmp1) = << sizeof(bmp1) << endl;
    cout
    << sizeof(bmp2) = << sizeof(bmp2) << endl;
    cout
    << sizeof(bmp3) = << sizeof(bmp3) << endl;
    cout
    << sizeof(bmp4) = << sizeof(bmp4) << endl;

    //输出个成员变量指针的值
    cout << 各成员变量指针的值 << endl;
    printf(
    &Top::_top = %d\n, &Top::_top);
    printf(
    tmp1 = %d\n, tmp1);
    cout
    << endl;
    printf(
    &Left::_top = %d\n, &Left::_top);
    printf(
    lmp1 = %d\n, lmp1);
    printf(
    &Left::_left = %d\n, &Left::_left);
    printf(
    lmp2 = %d\n, lmp2);
    cout
    << endl;
    printf(
    &Right::_top = %d\n, &Right::_top);
    printf(
    rmp1 = %d\n, rmp1);
    printf(
    &Right::_right = %d\n, &Right::_right);
    printf(
    rmp2 = %d\n, rmp2);
    cout
    << endl;
    printf(
    &Bottom::_top = %d\n, &Bottom::_top);
    printf(
    bmp1 = %d\n, bmp1);
    printf(
    &Bottom::_left = %d\n, &Bottom::_left);
    printf(
    bmp2 = %d\n, bmp2);
    printf(
    &Bottom::_right = %d\n, &Bottom::_right);
    printf(
    bmp3 = %d\n, bmp3);
    printf(
    &Bottom::_bottom = %d\n, &Bottom::_bottom);
    printf(
    bmp4 = %d\n, bmp4);

    bp
    ->bmp1 = 1;
    bp
    ->bmp2 = 2;
    bp
    ->bmp3 = 3;
    bp
    ->bmp4 = 4;

    bmp1
    = tmp1;
    bmp2
    = lmp2;
    bmp3
    = rmp2;

    }


    下面是法度运行的成果:



    经由过程法度运行成果,我们可以获得2种信息:


    1 包含虚拟持续的时辰,成员变量指针仍然指向的不是成员变量在内存中的真正地址


    2 包含虚拟持续的时辰,成员变量指针都为8字节,而不是4字节,比如c++代码中除了tmp1成员变量指针之外的其他成员变量指针的大小


    那么,包含虚拟持续的时辰,成员变量指针存储的值是什么,为什么会是8字节?下面经由过程解析来解析类Bottom中成员变量指针定义的汇编代码:



    ; 40   :   //Bottom中的成员变量指针
    
    ;
    41 : int Bottom::bmp1 = &Bottom::_top;

    mov DWORD PTR ¥T24720[ebp], 0;将0写入姑且对象ST24720的首地址处内存
    mov DWORD PTR ¥T24720[ebp+4], 4;将4写入偏移姑且对象ST24720首地址4byte处内存
    mov ecx, DWORD PTR ¥T24720[ebp];将姑且对象ST24720首地址处内存内容给存放器ecx
    mov DWORD PTR _bmp1¥[ebp], ecx;将存放器ecx的值写入bmp1首地址处内存
    mov edx, DWORD PTR ¥T24720[ebp+4];将偏移姑且对象ST24720首地址4byte处内存内容给存放器edx
    mov DWORD PTR _bmp1¥[ebp+4], edx;将存放器edx的内容给偏移bmp1首地址4byte处内存

    ; 42 : int Bottom::bmp2 = &Bottom::_left;
    ;过程同bmp1,只是存储的值不合

    mov DWORD PTR ¥T24721[ebp], 4
    mov DWORD PTR ¥T24721[ebp+4], 0
    mov eax, DWORD PTR ¥T24721[ebp]
    mov DWORD PTR _bmp2¥[ebp], eax
    mov ecx, DWORD PTR ¥T24721[ebp+4]
    mov DWORD PTR _bmp2¥[ebp+4], ecx

    ; 43 : int Bottom::bmp3 = &Bottom::_right;
    ;和bmp1有点不一样 这里仅就汇编法度的实际履行流程来解析,其它的忽视
    xor edx, edx;将存放器edx里面的内容异或运算,这时,不管edx里面是什么,此时存的值必然是0
    cmp edx, -1;将edx的值同-1斗劲
    jne SHORT ¥LN9@main;按照斗劲成果,若是edx的值不是-1,就跳到标号¥LN9@main处执履行,这里显然是要跳转履行
    mov DWORD PTR ¥T24722[ebp], 0
    mov DWORD PTR ¥T24722[ebp+4], -1
    mov eax, DWORD PTR ¥T24722[ebp]
    mov DWORD PTR ¥T24726[ebp], eax
    mov ecx, DWORD PTR ¥T24722[ebp+4]
    mov DWORD PTR ¥T24726[ebp+4], ecx
    jmp SHORT ¥LN10@main
    LN9@main:
    xor edx, edx;将edx存放器里面的内容异或运算,这时,不管edx里面是什么,此时存的值必然是0
    jne SHORT ¥LN7@main;若是异或的成果不为零,就跳转到标号¥LN7@main处履行,不然,次序履行,这里显然是次序履行
    mov DWORD PTR tv89[ebp], 8;将8给姑且变量tv89(8正好是sizof(Left))
    jmp SHORT ¥LN8@main;跳转到标号¥LN8@main处履行
    LN7@main:
    mov DWORD PTR tv89[ebp], 0
    LN8@main:
    mov eax, DWORD PTR tv89[ebp];将tv89的值给存放器eax
    add eax, 4;将存放器eax里面的值加上4 (4正好是父类Right子对象中vbtable指针的大小)
    mov DWORD PTR ¥T24723[ebp], eax;将存放器eax的值写入姑且对象ST24723首地址处内存
    mov DWORD PTR ¥T24723[ebp+4], 0;将0写入偏移姑且对象首地址4byte处内存
    mov ecx, DWORD PTR ¥T24723[ebp];将姑且对象ST24723首地址处内容给存放器ecx
    mov DWORD PTR ¥T24726[ebp], ecx;将存放器ecx的值写入姑且对象ST24726的首地址处内存
    mov edx, DWORD PTR ¥T24723[ebp+4];将偏移姑且对象ST24723首地址4byte处内存内容给存放器edx
    mov DWORD PTR ¥T24726[ebp+4], edx;将存放器edx的内容给偏移姑且对象ST24726首地址4byte处内存
    LN10@main:
    mov eax, DWORD PTR ¥T24726[ebp];将姑且对象ST24726首地址处内存给存放器eax
    mov DWORD PTR _bmp3¥[ebp], eax;将存放器eax的内容给bmp3首地址处内存
    mov ecx, DWORD PTR ¥T24726[ebp+4];将偏移姑且对象ST24726首地址4byte处内存内容给存放器ecx
    mov DWORD PTR _bmp3¥[ebp+4], ecx;将存放器ecx的内容给偏移bmp3首地址4byte处内存内容

    ; 44 : int Bottom::bmp4 = &Bottom::_bottom;
    ;过程同bmp1,只是存储的值不合

    mov DWORD PTR ¥T24729[ebp], 16 ; 00000010H
    mov DWORD PTR ¥T24729[ebp+4], 0
    mov edx, DWORD PTR ¥T24729[ebp]
    mov DWORD PTR _bmp4¥[ebp], edx
    mov eax, DWORD PTR ¥T24729[ebp+4]
    mov DWORD PTR _bmp4¥[ebp+4], eax


    可以看到,bmp1~bmp4被当成了对象对待,其里面存储的值如下:


            


    里面存储的值知道了,然则到底都有什么意义呢?下面我们就来看用着4个成员变量指针操纵响应成员变量的汇编码:



    82   :   bp->bmp1 = 1;
    

    mov ecx, DWORD PTR _bp¥[ebp];将对象b的首地址给存放器ecx
    mov edx, DWORD PTR [ecx];将对象b首地址处内容(即vbtable的首地址)给存放器edx
    mov eax, DWORD PTR _bmp1¥[ebp+4];将偏移bmp1首地址4byte处内容(即4)给存放器eax
    mov ecx, DWORD PTR _bp¥[ebp];将对象b首地址给存放器ecx
    add ecx, DWORD PTR [edx+eax];存放器edx里面存储vbtable首地址,而eax存储的是4
    ;是以edx+eax仍然是偏移vbtable首地址4byte处内存地址,所以这条指令是
    ;获取偏移vbtable首地址4byte处内存内容(存储的是vbtable指针偏移虚基类Top子对象首地址的偏移量,为20)
    ;与存放器ecx内容相加,成果保存到存放器ecx
    ;所以,ecx里面保存的是虚基类Top子对象的首地址
    mov edx, DWORD PTR _bmp1¥[ebp];将bmp1首地址处内存内容(即0)给存放器edx
    mov DWORD PTR [ecx+edx], 1;存放器ecx保存虚基类Top子对象首地址,存放器edx里面内容为0 所以ecx+edx仍然是虚基类
    ;Top子对象首地址,这里将1写入虚基类首地址处内存,即给对象b成员变量_top赋值

    ; 83 : bp->bmp2 = 2;

    mov eax, DWORD PTR _bp¥[ebp];将对象b的首地址给存放器eax
    mov ecx, DWORD PTR [eax];获取对象b首地址内容(即vbtable的首地址)给存放器ecx
    mov edx, DWORD PTR _bmp2¥[ebp+4];将偏移bmp2首地址4byte处内存内容(即0)给存放器edx
    mov eax, DWORD PTR _bp¥[ebp];将对象b首地址给存放器eax
    add eax, DWORD PTR [ecx+edx];存放器ecx里面存的是vbtable首地址,edx里面存的是0 所以ecx+edx
    ;仍然是vbtable首地址,这里获取的是vbtable首地址处内容(即vbtable指针偏移对象b首地址的偏移量,为0)
    ;所以,这里取vbtable首地址处内存内容,在和存放器eax里面内容相加,成果保存到eax里面
    ;此时eax保存的是对象b的首地址
    mov ecx, DWORD PTR _bmp2¥[ebp];将bmp2首地址处的内容(即4)给存放器ecx
    mov DWORD PTR [eax+ecx], 2;eax保存对象b的首地址 ecx内容为4 eax+ecx为对象b成员变量_left实际的内存地址
    ;是以这里是给对象b的成员变量_left赋值

    ; 84 : bp->bmp3 = 3;
    ;和bp->bmp3 = 2的操纵类似,只是数据不合

    mov edx, DWORD PTR _bp¥[ebp]
    mov eax, DWORD PTR [edx]
    mov ecx, DWORD PTR _bmp3¥[ebp+4]
    mov edx, DWORD PTR _bp¥[ebp]
    add edx, DWORD PTR [eax+ecx]
    mov eax, DWORD PTR _bmp3¥[ebp]
    mov DWORD PTR [edx+eax], 3

    ; 85 : bp->bmp4 = 4;
    ;和bp->bmp2 = 2的操纵类似,只是数据不合

    mov ecx, DWORD PTR _bp¥[ebp]
    mov edx, DWORD PTR [ecx]
    mov eax, DWORD PTR _bmp4¥[ebp+4]
    mov ecx, DWORD PTR _bp¥[ebp]
    add ecx, DWORD PTR [edx+eax]
    mov edx, DWORD PTR _bmp4¥[ebp]
    mov DWORD PTR [ecx+edx], 4


    经由过程汇编码我们可以发明,bmp1~bmp4中所存储的值的意义,第一项仍然是偏移成员变量所属类对象首地址的偏移量(然则虚基类成员变量指针有点不一样,比如bmp1第一项存储的是0,是对象b的_top成员变量相对于虚基类Top子对象首地址的偏移量,而不是相对于对象b的首地址偏移量);而第二项是偏移vtable首地址的偏移量。成员变量指针经由过程这两个数据,以及绑定的对象或者对象指针(仍然相当于this指针容器)来策画出成员变量在内存中的真正地址。至于lmp1~lmp2 rmp1~rmp2所存储的值,和bmp1~bmp2类似。下面是Top Left Right Bottom的内存布局:


         


      



         


    之所以包含虚拟持续的成员变量指针须要额外的字节来存储信息,就是因为当存在虚基类的时辰,虚基类的地位是不固定的。比如,若是Bottom又派生了一个SubBottom子类(非虚拟持续),且该子类引入了一个新的成员变量int  _subBottom,那么SubBottom的内存布局中,_subBottom就会加到_bottom的后面,_top的前面。如许,_top在类Bottom中心隔首地址是20,在SubBottom中就会成为24,而_left和_right地位不会变,在SubBottom中心隔其首地址仍然是4和12。类似的,若是SubBottom也派生了一个子类(非虚拟持续)SubSubBotom,q且该子类也引入了一个成员变量int _subSubBottom,类SubSubBottom的内存布局中,subSubBottom就会加到subBottom后面,_top前面,如许,_top间隔SubSubBottom的首地址偏移量变成了28,而_left _right仍然是4和12.然则,其在虚基类Top里面的偏移量是固定的,老是0.所以经由过程虚基类成员变量指针,比如bmp1来操纵虚基类成员变量,老是要先定位响应的虚基类首地址,然后经由过程虚基类成员变量(_top)相对于虚基类(Top)首地址的偏移量来得出虚基类成员变量(_top)在内存中的真正地址。而要定位虚基类(Top)首地址,就要有响应的vtable信息,这就是成员变量指针中的额外字节存储的信息。


     成员变量指针之间的转换


    这种景象之下,基类成员变量指针也可以转换成派生类成员变量指针,因为基类中的成员变量必然存在于派生类中,然则,派生类成员变量指针无法转换成基类成员变量指针,因为派生类中存在的成员变量,基类中不必然存在。和从汇编看c++中指向成员变量的指针(一)中讲的一样,对于bmp1(8byte) = tmp1(4byte) bmp2 = lmp2 bmp3 = rmp3的转化,并不是将后者的值赋给前者,而是编译器内部进行转化,结果就和直接让bmp1 = &Bottom::_top bmp2 = &Bottom::_left bmp3 = &Bottom::_right一样





    读书,不要想着实用,更不要有功利心。读书只为了自身的修养。邂逅一本好书如同邂逅一位知己,邂逅一个完美之人。有时心生敬意,有时怦然心动。仿佛你心底埋藏多年的话,作者替你说了出来,你们在时光深处倾心相遇的一瞬间,情投意合,心旷神怡。
    分享到: