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