} } }

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

    添加时间:2013-6-23 点击量:

    在c++中,指向类成员变量的指针存储的并不是该成员变量地点内存的地址,而仅仅是该成员变量在该类对象中相对于对象首地址的偏移量。


    下面先看c++源码以及输出成果:



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

    class X {
    public:
    int _x;
    };
    class Y {
    public:
    int _y;
    };

    class Z : public X, public Y {
    public:
    int _z;

    };

    int main() {
    //机关x,y,z三个对象
    X x;
    Y y;
    Z z;
    //将三个对象里面的成员值置为1
    x._x = 1;
    y._y
    = 1;
    z._x
    = 1;
    z._y
    = 1;
    z._z
    = 1;
    //x的成员指针
    int X::xmp1 = &X::_x;
    int X::xmp2 = &Z::_x;
    //y的成员指针
    int Y::ymp1 = &Y::_y;
    int Y::ymp2 = &Z::_y;
    //z的成员指针
    int Z::zmp1 = &X::_x;
    int Z::zmp2 = &Z::_x;
    int Z::zmp3 = &Y::_y;
    int Z::zmp4 = &Z::_y;
    int Z::zmp5 = &Z::_z;
    //输出x的成员指针值
    cout << 输出x的成员指针值: << endl;
    printf(
    &X::_X = %d\n, &X::_x);
    printf(
    &Z::_x = %d\n, &Z::_x);
    printf(
    X::xmp1 = %d\n, xmp1);
    printf(
    X::xmp2 = %d\n, xmp2);
    cout
    << 输出y的成员指针值: << endl;
    printf(
    &Y::_y = %d\n, &Y::_y);
    printf(
    &Z::_y = %d\n, &Z::_y);
    printf(
    Y::ymp1 = %d\n, ymp1);
    printf(
    Y::ymp2 = %d\n, ymp2);
    cout
    << 输出z的成员指针值: << endl;
    printf(
    &X::_X = %d\n, &X::_x);
    printf(
    &Z::_x = %d\n, &Z::_x);
    printf(
    &Y::_y = %d\n, &Y::_y);
    printf(
    &Z::_y = %d\n, &Z::_y);
    printf(
    &Z::_z = %d\n, &Z::_z);
    printf(
    Z::zmp1 = %d\n, zmp1);
    printf(
    Z::zmp2 = %d\n, zmp2);
    printf(
    Z::zmp3 = %d\n, zmp3);
    printf(
    Z::zmp4 = %d\n, zmp4);
    printf(
    Z::zmp5 = %d\n, zmp5);
    //将基类X,Y的成员指针和派生类Z绑定
    Z zp = &z;
    cout
    << 输出由z绑定x的成员指针: << endl;
    cout
    << 由对象绑定: << endl;
    z.
    xmp1 = 2;//对象绑定
    cout << x.xmp1 = << x.xmp1 << endl;
    cout
    << z.xmp1 = << z.xmp1 << endl;
    cout
    << x._x = << x._x << endl;

    cout
    << z._x = << z._x << endl;
    z.
    xmp2 = 3;
    cout
    << x.xmp2 = << x.xmp2 << endl;
    cout
    << z.xmp2 = << z.xmp2 << endl;
    cout
    << x._x = << x._x << endl;
    cout
    << z._x = << z._x << endl;
    cout
    << 由指针绑定: << endl;
    zp
    ->xmp1 = 4;
    cout
    << x.xmp1 = << x.xmp1 << endl;
    cout
    << zp->xmp1 = << zp->xmp1 << endl;
    cout
    << x._x = << x._x << endl;
    cout
    << z._x = << z._x << endl;
    zp
    ->xmp2 = 5;
    cout
    << x.xmp2 = << x.xmp2 << endl;
    cout
    << zp->xmp2 = << zp->xmp2 << endl;
    cout
    << x._x = << x._x << endl;
    cout
    << z._x = <<z._x << endl;
    cout
    << 输出由z绑定y的成员指针: << endl;
    cout
    << 由对象绑定: << endl;
    z.
    ymp1 = 6;
    cout
    << y.ymp1 = << y.ymp1 << endl;
    cout
    << z.ymp1 = << z.ymp1 << endl;
    cout
    << y._y = << y._y << endl;
    cout
    << z._y = << z._y << endl;
    z.
    ymp2 = 7;
    cout
    << y.ymp2 = << y.ymp2 << endl;
    cout
    << z.ymp2 = << z.ymp2 << endl;
    cout
    << y._y = << y._y << endl;
    cout
    << z._y = << z._y << endl;
    cout
    << 由指针绑定: << endl;
    zp
    ->ymp1 = 8;
    cout
    << y.ymp1 = << y.ymp1 << endl;
    cout
    << zp->ymp1 = << zp->ymp1 << endl;
    cout
    << y._y = << y._y << endl;
    cout
    << z._y = << z._y << endl;
    zp
    ->ymp2 = 9;
    cout
    << y.ymp2 = << y.ymp2 << endl;
    cout
    << zp->ymp2 = << zp->ymp2 << endl;
    cout
    << y._y = << y._y << endl;
    cout
    << z._y = << z._y << endl;
    //将zp指针向上转换为X 和 Y 再操纵它们的成员指针
    cout << 将zp向上转换为X: << endl;
    X
    xp = zp;
    xp
    ->xmp1 = 10;
    cout
    << x.xmp1 = << x.xmp1 << endl;
    cout
    << xp->xmp1 = << xp->xmp1 << endl;
    cout
    << x._x = << x._x << endl;
    cout
    << z._x = << z._x << endl;
    xp
    ->xmp2 = 11;
    cout
    << x.xmp2 = << x.xmp2 << endl;
    cout
    << xp->xmp2 = << xp->xmp2 << endl;
    cout
    << x._x = << x._x << endl;
    cout
    << z._x = << z._x << endl;
    cout
    << 将zp向上转换为Y: << endl;
    Y
    yp = zp;
    yp
    ->ymp1 = 12;
    cout
    << y.ymp1 = << y.ymp1 << endl;
    cout
    << yp->ymp1 = << yp->ymp1 << endl;
    cout
    << y._y = << y._y << endl;
    cout
    << z._y = << z._y << endl;
    yp
    ->ymp2 = 13;
    cout
    << y.ymp2 = << y.ymp2 << endl;
    cout
    << yp->ymp2 = << yp->ymp2 << endl;
    cout
    << y._y = << y._y << endl;
    cout
    << z._y = << z._y << endl;





    }


    下面是输出的成果,因为运行成果在cmd上斗劲长,是以分隔截图:



    下面是类的持续关系图:



    下面是每一个类的内存布局:


          


    经由过程上方c++代码的输出成果可以发明,若是直接输出成员的偏移量,就是下面的代码一样:



     printf(&X::_X = %d\n, &X::_x);
    
    printf(
    &Z::_x = %d\n, &Z::_x);
    printf(
    &Y::_y = %d\n, &Y::_y);
    printf(
    &Z::_y = %d\n, &Z::_y);
    printf(
    &Z::_z = %d\n, &Z::_z);


    获得的始终是该成员变量在最原始的类(即声明这个成员变量的类)里面的偏移量,即输出_x的偏移量时,获得的是_x在类X中的偏移量;输出_y的偏移量时,获得的是_y在类Y中的偏移量;输出_z时,获得是四核_z在类Z中的偏移量。所以上方的输出成果依次是0 0 0 0 8


    然则,若是将他们赋给了指定类型的成员变量指针,就会输出该成员变量在指针绑定的类里面的偏移量,就像下面的代码:




    //z的成员指针
    
    int Z::zmp1 = &X::_x;
    int Z::zmp2 = &Z::_x;
    int Z::zmp3 = &Y::_y;
    int Z::zmp4 = &Z::_y;
    int Z::zmp5 = &Z::_z;


    因为这些成员变量指针绑定的都是类Z,依次输出成员变量指针的值时,获得的都邑是相对于该类的偏移量,所以上方输出的成果依次是0 0 4 4 8
    当将基类成员变量指针绑定到派生类对象上的时辰,操纵的是派生类对象中对应的成员变量,就像下面的代码:



     z.ymp1 = 6;



     zp->ymp1 = 8;


    成员变量指针ypm1底本存储的是类Z的基类Y的成员_y的偏移量0,然则若是将它绑定到派生类Z上,不管是用对象z本身或者是对象指针zp绑定,按理应当操纵的是对象z中的_x成员,然则,从输出成果来看,仍然操纵的是对象z中的_y成员变量。到底是什么原因,下面是zp->ymp1对应的汇编码(z.ymp1的道理一样):



    ; 103  :    zp->ymp1 = 8;
    

    cmp DWORD PTR _zp¥[ebp], 0;将zp指针的值和0斗劲,以防zp指针为空
    je SHORT ¥LN11@main;若是上方的斗劲为0,就跳转到标号¥LN11@main处履行,不然次序履行 这里次序履行
    mov eax, DWORD PTR _zp¥[ebp];将对象z首地址(保存在zp中)给存放器eax
    add eax, 4;对象z的首地址加上4 获得是对象z中父类Y对象的首地址,存于存放器eax
    mov DWORD PTR tv519[ebp], eax;将父类Y对象的首地址给姑且变量tv519
    jmp SHORT ¥LN12@main;跳转到标号¥LN12@main处履行
    LN11@main:
    mov DWORD PTR tv519[ebp], 0;将0给姑且变量tv519
    LN12@main:
    mov ecx, DWORD PTR tv519[ebp];将姑且变量tv519的值给存放器ecx 若是zp不为空,此时ecx中存储的是父类Y对象的首地址
    add ecx, DWORD PTR _ymp1¥[ebp];将成员指针ymp1的值加上父类Y对象的首地址,获得_y在对象z中的真正内存地址,存于ecx
    mov DWORD PTR [ecx], 8;将8写入ecx存储的内存单位里面,即将对象z中的_y成员变量赋值为8


    下面是z.ymp1对应的汇编码:



    ; 92   :    z.ymp1 = 6;
    

    lea edx, DWORD PTR _z¥[ebp];取对象z的首地址,存放到存放器edx
    test edx, edx;测试edx存储的值是否为0 即看对象的首地址是否为空
    ;下面的汇编代码根蒂根基与zp->ymp1 = 8的一样
    je SHORT ¥LN3@main
    lea eax, DWORD PTR _z¥[ebp]
    add eax, 4
    mov DWORD PTR tv410[ebp], eax
    jmp SHORT ¥LN4@main
    LN3@main:
    mov DWORD PTR tv410[ebp], 0
    LN4@main:
    mov ecx, DWORD PTR tv410[ebp]
    add ecx, DWORD PTR _ymp1¥[ebp]
    mov DWORD PTR [ecx], 6


    可以看到,编译器在内部实际上做了转换 换成c++的表达情势即: zp ? (zp + sizeof(X)) : 0  zp + ymp1 也就是说编译器内部先做了指针调剂,使其指向了正确的父类对象首地址。也就是说将zp的类型从Z转换成了Y(向上转换),这是容许的,然则,若是有一个Y yp指针,如许操纵yp->zmp3如许是不容许的,因为这实际上是要将yp的类型从Y转换成了Z(向下转换)。


    至于将zp指针分别转化成xp和yp指针,在操纵基类成员变量指针,道理和上方一样,只不过转换指针的过程由我们本身完成,即分别将zp的类型转换成了X和Y




    无论对感情还是对生活,“只要甜不要苦”都是任性而孩子气的,因为我们也不完美,我们也会伤害人。正因为我们都不完美,也因为生活从不是事事如意,所以对这些“瑕疵”的收纳才让我们对生活、对他人的爱变得日益真实而具体。—— 汪冰《世界再亏欠你,也要敢于拥抱幸福》
    分享到: