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