下面是c++源码:

class Top {//虚基类
public:
int i;
Top(int ii) {
i = ii;
}
virtual int getTop() {
cout << (long)this << endl;
return ;
}
}; class Left : public virtual Top {
public:
int j;
Left(int jj, int ii) : Top(ii) {
j = jj;
}
int getTop() {
return ;
}
virtual int getLeft() {
return ;
}
}; class Right : public virtual Top {
public:
int k;
Right(int kk, int ii) : Top(ii) {
k = kk;
}
int getTop() {
return ;
}
virtual int getRight() {
return ;
}
}; class Bottom : public Left, public Right {
public:
int l;
Bottom(int ll, int jj, int kk, int ii) : Top(ii), Left(jj, ii), Right(kk, ii) {
l = ll;
}
int getTop() {
cout << (long)this << endl;
return ;
}
int getLeft() {
return ;
}
int getRight() {
return ;
}
virtual int getBottom() {
return ;
}
}; int main() {
Bottom b(, , , );
Bottom* bp = &b;
//向上转换为Left
Left* lp = bp;
int lleft = lp->getLeft();
int ltop = lp->getTop();
//向上转换为Right
Right* rp = bp;
int rright = rp->getRight();
int rtop = rp->getTop();
//向上转换为Top
Top* tp = bp;
int ttop =tp->getTop();
//bp指针自己调用
int btop = bp->getTop();
int bleft = bp->getLeft();
int bright = bp->getRight();
int bbottom = bp->getBottom();
};

其中,每一个类都有自己独有的一个虚函数,子类都会复写从父类继承而来的虚函数。

下面是main函数中的汇编码:

; 60   : int main() {

    push    ebp
mov ebp, esp
sub esp, ; 为程序中的变量预留存储空间,其中对象b占用40byte ; 61 : Bottom b(1, 2, 3, 4); push ;压入标志,1表示调用虚基类构造函数 0表示不调用虚基类构造函数
push ;压栈4,为对象b构造函数传递参数
push ;压栈3,为对象b构造函数传递参数
push ;压栈2,为对象b构造函数传递参数
push ;压栈1,为对象b构造函数传递参数
lea ecx, DWORD PTR _b$[ebp];将对象b的首地址给寄存器ecx,作为隐含参数传递给b的构造函数
call ??0Bottom@@QAE@HHHH@Z ; 调用对象b的构造函数 ; 62 : Bottom* bp = &b; lea eax, DWORD PTR _b$[ebp];获取对象b的首地址给寄存器eax
mov DWORD PTR _bp$[ebp], eax;将对象b的首地址给指针bp ; 63 : //向上转换为Left
; 64 : Left* lp = bp; mov ecx, DWORD PTR _bp$[ebp];将对象b的首地址给寄存器ecx
mov DWORD PTR _lp$[ebp], ecx;将对象b的首地址给指针lp(因为父类Left对象首地址和对象b首地址一样) ; 65 : int lleft = lp->getLeft(); mov edx, DWORD PTR _lp$[ebp];将父类Left对象首地址给edx寄存器
mov eax, DWORD PTR [edx];获取寄存器edx里面的内容(即vftable,虚表首地址)给寄存器eax
mov ecx, DWORD PTR _lp$[ebp];将父类Left对象首地址给寄存器ecx
mov edx, DWORD PTR [eax];将虚表首地址处内存内容(即虚函数getLeft的首地址)给寄存器edx
call edx;调用getLeft函数
mov DWORD PTR _lleft$[ebp], eax;寄存器eax里面含有getLeft函数返回的结果,写入变量lleft里面 ; 66 : int ltop = lp->getTop(); mov eax, DWORD PTR _lp$[ebp];获取父类对象Left的首地址给寄存器eax
mov ecx, DWORD PTR [eax+];获取偏移父类对象Left首地址4byte处内存内容(即vbtable的首地址)给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移vbtable首地址4byte处内存内容(即虚基类Top对象的虚表指针偏移父类Left对象vbtable指针的偏移量),给寄存器edx
mov eax, DWORD PTR _lp$[ebp];获父类对象Left的首地址给寄存器eax
mov ecx, DWORD PTR [eax+];获取偏移父类对象Left首地址4byte处内存内容(即vbtable的首地址)给寄存器ecx
mov eax, DWORD PTR [ecx+];获取偏移vbtable首地址4byte处内存内容(即虚基类Top对象的虚表指针偏移父类Left对象vbtable指针的偏移量),给寄存器eax
mov ecx, DWORD PTR _lp$[ebp];获取父类对象Left的首地址给寄存器ecx
lea ecx, DWORD PTR [ecx+eax+];ecx存放父类Left首地址,eax存放虚基类Top到父类Left对象的vbtable指针处的偏移量,两者相加,再加上
;父类Left对象自身的vptr指针大小4byte,得到虚基类Top对象首地址,给寄存器ecx
mov eax, DWORD PTR _lp$[ebp];获取父类Left对象的首地址给寄存器eax
mov edx, DWORD PTR [eax+edx+];寄存器eax存放父类Left对象首地址,寄存器edx存放虚基类Top到父类对象vbtable指针处偏移量,两者相加,
;再加上父类Left对象自身的vptr指针大小4byte,得到虚基类Top对象的首地址,然后在取虚基类Top对象首地址
;处内容(即vftable首地址)给寄存器edx
mov eax, DWORD PTR [edx];将虚表首地址处内容(即虚函数getTop的首地址)给寄存器eax
call eax;调用虚函数getTop
mov DWORD PTR _ltop$[ebp], eax;寄存器eax里面含有调用getTop函数的返回值,写入变量ltop ; 67 : //向上转换为Right
; 68 : Right* rp = bp; cmp DWORD PTR _bp$[ebp], ;比较对象b首地址是否为0,即判断bp指针是否为空
je SHORT $LN3@main;如果bp指针为空,就会跳转到标号$LN3@main处执行,否则顺序执行 这里顺序执行
mov ecx, DWORD PTR _bp$[ebp];获取对象b的首地址给寄存器ecx
add ecx, ; 将对象b的首地址加上12,的到父类Right对象首地址,存到寄存器ecx
mov DWORD PTR tv133[ebp], ecx;将父类Right的首地址给临时变量tv133
jmp SHORT $LN4@main;跳转掉标号$LN4@main处执行
$LN3@main:
mov DWORD PTR tv133[ebp], ;如果指针bp为空指针,临时变量tv133将会赋值0
$LN4@main:
mov edx, DWORD PTR tv133[ebp];将临时变量tv133的值给寄存器edx
mov DWORD PTR _rp$[ebp], edx;将寄存器edx的内容给rp指针。要是bp指针判断不为空,rp指针保存的就是父类Right对象首地址
;这里完成了bp指针到rp指针的转化
;在转化的过程中之所以要判断bp指针是否为0,是因为rp指针所指向的内存地址,是由bp指向的内存地址
;加上一定的偏移量得来(这里是12byte),如果不进行判断,一旦bp为空指针,rp就会指向错误的内存。编译器必须防止
;这类错误发生 ; 69 : int rright = rp->getRight(); mov eax, DWORD PTR _rp$[ebp];将父类Right对象首地址给寄存器eax
mov edx, DWORD PTR [eax];将父类Right对象首地址处内容(即vftable首地址)给寄存器edx
mov ecx, DWORD PTR _rp$[ebp];获取父类Right对象的首地址,给寄存器ecx
mov eax, DWORD PTR [edx];获取虚表首地址处的内容(即虚函数getRigt首地址)给寄存器eax
call eax;调用getRight函数
mov DWORD PTR _rright$[ebp], eax;eax里面含有调用getRight函数后的返回结果,这里将结果写给rright变量 ; 70 : int rtop = rp->getTop(); mov ecx, DWORD PTR _rp$[ebp];将父类Right对象的首地址给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移父类Right对象首地址4byte处内存内容(即vbtable首地址)给寄存器edx
mov eax, DWORD PTR [edx+];获取偏移vbtable首地址4byte处内存内容(即虚基类Top的虚表指针偏移vbtable指针的偏移量),给寄存器eax
mov ecx, DWORD PTR _rp$[ebp];获取父类Right对象的首地址给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移父类Right对象首地址4byte处内存内容(即vbtable首地址)给寄存器edx
mov ecx, DWORD PTR [edx+];获取偏移vbtable首地址4byte处内存内容(即虚基类Top的虚表指针偏移vbtable指针的偏移量),给寄存器ecx
mov edx, DWORD PTR _rp$[ebp];获取父类Right对象的首地址给寄存器edx
lea ecx, DWORD PTR [edx+ecx+];edx寄存器里面保存的是父类Right对象首地址,ecx里面保存的是虚基类Top的虚表指针到父类Right对象vbtable的偏移量
;两者相加,再加上父类Right对象自身的vptr指针大小4byte,即的到虚基类Top对象首地址,将该地址保存到寄存器ecx
mov edx, DWORD PTR _rp$[ebp];获取父类Right对象首地址给寄存器edx
mov eax, DWORD PTR [edx+eax+];edx寄存器里面保存的是父类Right对象首地址,eax里面保存的是虚基类Top的虚表指针到父类Right对象vbtable的偏移量
;两者相加,再加上父类Right对象自身的vptr指针大小4byte,即的到虚基类Top对象首地址,将该地址处内容(即vftable首地址)
;给寄存器eax
mov edx, DWORD PTR [eax];获取vftable首地址处内容,即虚函数getTop的首地址,给寄存器edx
call edx;调用虚函数getTop
mov DWORD PTR _rtop$[ebp], eax;eax里面含有调用函数返回的结果,保存到变量rtop ; 71 : //向上转换为Top
; 72 : Top* tp = bp; cmp DWORD PTR _bp$[ebp], ;比较bp指针是否为0,即判断bp指针是否为空
jne SHORT $LN5@main;如果为空,就顺序执行,否则,就跳转到标号$LN5@main处执行 这里跳转到标号执行
mov DWORD PTR tv170[ebp], ;将0赋给临时变量tv170
jmp SHORT $LN6@main;跳转到标号$LN6@main执行
$LN5@main:
mov eax, DWORD PTR _bp$[ebp];将对象b首地址给寄存器eax
mov ecx, DWORD PTR [eax+];获取偏移对象b首地址4byte处内存内容(即vbtable首地址)给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移vbtable首地址4byte处内存内容,即虚基类Top虚表指针到vbtable指针的偏移量,给寄存器edx
mov eax, DWORD PTR _bp$[ebp];将对象b的首地址给寄存器eax
lea ecx, DWORD PTR [eax+edx+];eax里面保存对象b的首地址,edx里面保存虚基类Top虚表指针对象b的vbtable指针的偏移量,
;两者相加,在加上对象b自身的vtpr指针大小(4byte),获得虚基类Top的首地址,存到寄存器ecx
mov DWORD PTR tv170[ebp], ecx;将虚基类Top的首地址给临时变量tv170
$LN6@main:
mov edx, DWORD PTR tv170[ebp];将tv170里面的内容给寄存器edx
mov DWORD PTR _tp$[ebp], edx;将寄存器edx里面的内容给tp指针。如果bp指针不为空,那么tp指针保存的就是虚基类Top的首地址
;到这里,由bp指针转换到top指针结束 ; 73 : int ttop =tp->getTop(); mov eax, DWORD PTR _tp$[ebp];获取虚基类首地址给寄存器eax
mov edx, DWORD PTR [eax];将虚基类Top首地址处内存内容给寄存器edx,即将vftable首地址给寄存器edx
mov ecx, DWORD PTR _tp$[ebp];将虚基类首地址给寄存器eax
mov eax, DWORD PTR [edx];将虚表首地址处内容(即虚函数Top的首地址)给寄存器eax
call eax;调用虚函数getTop
mov DWORD PTR _ttop$[ebp], eax;eax寄存器里面保存函数调用结果,写入变量ttop ;75
; 76 : int btop = bp->getTop(); mov ecx, DWORD PTR _bp$[ebp];获取对象b的首地址给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移对象首地址4字节处的内存内容(即vbtable首地址)给寄存器edx
mov eax, DWORD PTR [edx+];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移对象b的vbtable指针的偏移量),写入寄存器eax
mov ecx, DWORD PTR _bp$[ebp];获取对象b的首地址ecx
mov edx, DWORD PTR [ecx+];获取偏移对象首地址4字节处的内存内容(即vbtable首地址)给寄存器edx
mov ecx, DWORD PTR [edx+];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移对象b的vbtable指针的偏移量),写入寄存器ecx
mov edx, DWORD PTR _bp$[ebp];获取对象b首地址给寄存器edx
lea ecx, DWORD PTR [edx+ecx+];edx里面保存有对象b的首地址,ecx里面保存虚基类Top虚表指针到对象b的vbtable指针的偏移量
;二者相加,再加上对象b自身的vptr指针大小4byte,得到虚基类Top首地址,存放到寄存器ecx
mov edx, DWORD PTR _bp$[ebp];获取对象b的首地址,给寄存器edx
mov eax, DWORD PTR [edx+eax+];edx里面保存有对象b的首地址,eax里面保存虚基类Top虚表指针到对象b的vbtable指针的偏移量
;二者相加,再加上对象b自身的vptr指针大小4byte,得到虚基类Top首地址,存放到寄存器eax
mov edx, DWORD PTR [eax];获取虚基类首地址处内容(即vftable首地址)给寄存器eax,即虚函数getTop首地址
call edx;调用虚函数getTop
mov DWORD PTR _btop$[ebp], eax;eax保存函数getTop调用的返回结果,存放到btop变量 ; 77 : int bleft = bp->getLeft(); mov eax, DWORD PTR _bp$[ebp];获取对象b的首地址保存到寄存器eax
mov edx, DWORD PTR [eax];获取对象b首地址处的内容(即vftable首地址)给寄存器edx
mov ecx, DWORD PTR _bp$[ebp];获取对象b的首地址给寄存器ecx
mov eax, DWORD PTR [edx];获取虚表首地址处内容给寄存器eax,即虚函数getLeft的首地址
call eax;调用虚函数getLeft
mov DWORD PTR _bleft$[ebp], eax;eax里面含有调用getLeft函数的返回结果,写入bleft变量 ; 78 : int bright = bp->getRight(); mov ecx, DWORD PTR _bp$[ebp];获取对象b首地址给寄存器ecx
add ecx, ; 将对象b的首地址加上12,即得到父类Right对象首地址,保存到寄存器ecx
mov edx, DWORD PTR _bp$[ebp];获取对象b的首地址,给寄存器edx
mov eax, DWORD PTR [edx+];edx寄存器保存对象b首地址,加上12得到父类Right对象首地址,然后将父类Right对象首地址
;处内容(即vftable首地址)给寄存器eax
mov edx, DWORD PTR [eax];获取虚表vftable首地址处内容(即虚函数getRight首地址),给寄存器edx
call edx;调用虚函数getRight
mov DWORD PTR _bright$[ebp], eax;eax里面保存调用函数getRight的结果,写入变量bright ; 79 : int bbottom = bp->getBottom(); mov eax, DWORD PTR _bp$[ebp];获取对象b的首地址,给寄存器eax
mov edx, DWORD PTR [eax];获取对象首地址内容(即vftable首地址)给寄存器edx
mov ecx, DWORD PTR _bp$[ebp];获取对象b首地址给寄存器ecx
mov eax, DWORD PTR [edx+];获取偏移虚表vftable首地址4字节处内存内容(即虚函数getBottom的首地址),给寄存器eax
call eax;调用虚函数getBottom
mov DWORD PTR _bbottom$[ebp], eax;eax里面保存调用函数getBottom后的结果,写入变量bbottom ; 80 : ; 81 :
; 82 : }; xor eax, eax
mov esp, ebp
pop ebp
ret
_main ENDP

下面是Bottom构造函数的汇编码:

??0Bottom@@QAE@HHHH@Z PROC                ; Bottom::Bottom, COMDAT
; _this$ = ecx ; 44 : Bottom(int ll, int jj, int kk, int ii) : Top(ii), Left(jj, ii), Right(kk, ii) { push ebp
mov ebp, esp
push ecx;压栈ecx的目的是为了保存对象b的首地址预留空间
mov DWORD PTR _this$[ebp], ecx;ecx寄存器里面保存着对象b的首地址,存到刚才预留的空间
cmp DWORD PTR _$initVBases$[ebp], ;_$initVBases所代表的内存存放着调用Bottom构造函数之前压入的标志,这里将标志与0进行比较
je SHORT $LN1@Bottom;比较结果为0,即标志为0,就跳转到标号$LN1@Bottom处执行,避免重复调用虚基类构造函数 否则顺序执行 这里顺序执行
mov eax, DWORD PTR _this$[ebp];将对象b的首地址给寄存器eax
mov DWORD PTR [eax+], OFFSET ??_8Bottom@@7BLeft@@@;将??_8Bottom@@7BLeft@@@所代表的内存地址(即vbtable首地址)写入偏移对象
;首地址4byte处内存,即赋值vbtable指针
mov ecx, DWORD PTR _this$[ebp];将对象首地址给寄存器ecx
mov DWORD PTR [ecx+], OFFSET ??_8Bottom@@7BRight@@@;将??_8Bottom@@7BRight@@@所代表的内存地址(即vbtable首地址)写入偏移对象
;首地址16byte处内存,即赋值vbtable指针
mov edx, DWORD PTR _ii$[ebp];获取参数ii的值,给寄存器edx
push edx;压栈edx,给虚基类构造函数传递参数
mov ecx, DWORD PTR _this$[ebp];将对象b的首地址给寄存器ecx
add ecx, ; 对象b的首地址加上32byte,得到虚基类Top首地址,存放到寄存器ecx,作为隐含参数传递给虚基类构造函数
call ??0Top@@QAE@H@Z ; 调用虚基类构造函数
$LN1@Bottom:
push ;压入标志0,用来判断调用Left构造函数时,是否调用虚基类的构造函数
mov eax, DWORD PTR _ii$[ebp];将参数ii的值给寄存器eax
push eax;压栈eax,为Left构造函数传递参数
mov ecx, DWORD PTR _jj$[ebp];获取参数jj的值给寄存器ecx
push ecx;压栈ecx,为Left构造函数传递参数
mov ecx, DWORD PTR _this$[ebp];获取对象b的首地址(也是父类Left对象的首地址),给寄存器ecx,作为隐含参数传递给Left构造函数
call ??0Left@@QAE@HH@Z ; 调用Left构造函数
push ;压入标志0 用来判断调用Right构造函数时,是否调用虚基类Top的构造函数
mov edx, DWORD PTR _ii$[ebp];获取参数ii,给寄存器edx
push edx;压栈edx,为Right构造函数传递参数
mov eax, DWORD PTR _kk$[ebp];获取参数kk,给寄存器eax
push eax;压栈eax,为Right构造函数传递参数
mov ecx, DWORD PTR _this$[ebp];获取对象b的首地址,给寄存器ecx
add ecx, ; 给对象b的首地址加上12,得到父类Right对象首地址,作为隐含参数传递给Right构造函数
call ??0Right@@QAE@HH@Z ; 调用Right构造函数
mov ecx, DWORD PTR _this$[ebp];获取对象b的首地址,给寄存器ecx
mov DWORD PTR [ecx], OFFSET ??_7Bottom@@6BLeft@@@;将??_7Bottom@@6BLeft@@@所代表的内存首地址(vftable首地址)写入对象b的首地址处
mov edx, DWORD PTR _this$[ebp];获取对象b的首地址
mov DWORD PTR [edx+], OFFSET ??_7Bottom@@6BRight@@@;将??_7Bottom@@6BRight@@@所代表的内存首地址(vftable首地址)写入偏移对象b首地址12byte处
;即写到了父类Right对象的首地址处
mov eax, DWORD PTR _this$[ebp];获取对象b的首地址,给寄存器eax
mov ecx, DWORD PTR [eax+];获取偏移对象b首地址4byte处内存内容(即vbtable首地址)给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移vbtable首地址4byte处内存内容(即虚基类Top的虚表指针偏移对象b的vbtable指针的偏移量),给寄存器edx
mov eax, DWORD PTR _this$[ebp];获取对象b的首地址给寄存器eax
mov DWORD PTR [eax+edx+], OFFSET ??_7Bottom@@6BTop@@@;eax存放对象b的首地址,edx存放虚基类Top的虚表指针偏移对象b的vbtable指针的偏移量
;两者相加,再加上对象b自身首地址处的vptr指针大小,得到虚基类Top的首地址
;将??_7Bottom@@6BTop@@@所代表的内存地址(vftable首地址)写入到虚基类首地址处
mov ecx, DWORD PTR _this$[ebp];获取对象b的首地址,给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移对象首地址4byte处内存内容(即vbtable首地址),给寄存器edx
mov eax, DWORD PTR [edx+];获取偏移vbtable首地址4byte处内容,即获取虚基类Top虚表指针偏移对象b的vbtable指针的偏移量,给寄存器eax
sub eax, ; 将上述获取的偏移量减28
mov ecx, DWORD PTR _this$[ebp];获取对象b的首地址,给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移对象首地址4byte处内存内容(即vbtable首地址),给寄存器edx
mov ecx, DWORD PTR [edx+];获取偏移vbtable首地址4byte处内容,即获取虚基类Top虚表指针偏移对象b的vbtable指针的偏移量,给寄存器ecx
mov edx, DWORD PTR _this$[ebp];获取对象b的首地址,给寄存器edx
mov DWORD PTR [edx+ecx], eax;edx保存对象那个b的首地址,ecx保存的是虚基类Top虚表指针偏移对象b的vbtable指针的偏移量,两者相加,得?
;vtordisp的内存地址(关于vtordisp看文章下面提供的连接),将eax的值写入该内存
;通过下面的内存布局图,可以看到eax的值为0 ; 45 : l = ll; mov eax, DWORD PTR _this$[ebp];获取对象b的首地址,给寄存器eax
mov ecx, DWORD PTR _ll$[ebp];获取参数ll,给寄存器ecx
mov DWORD PTR [eax+], ecx;将参数ll的值写入偏移对象b首地址24byte处的内存,即为成员变量l赋值 ; 46 : } mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret ; 00000014H
??0Bottom@@QAE@HHHH@Z ENDP ; Bottom::Bottom

下面是Left构造函数的汇编码:

??0Left@@QAE@HH@Z PROC                    ; Left::Left, COMDAT
; _this$ = ecx ; 16 : Left(int jj, int ii) : Top(ii) { push ebp
mov ebp, esp
push ecx;压栈ecx的目的,是为保留类Left的对象首地址预留空间
mov DWORD PTR _this$[ebp], ecx;ecx寄存器保留类Left对象首地址,存放到刚才预留的空间
cmp DWORD PTR _$initVBases$[ebp], ;_$initVBases所带表的内存存放着调用Left构造函数之前传入的标志
;这里将标志与0比较
je SHORT $LN1@Left;如果标志为0,则跳到标号$LN1@Left处执行,不会调用虚基类的构造函数,否则,顺序执行,这里跳到标号处执行
mov eax, DWORD PTR _this$[ebp];获取类Left对象的首地址,给寄存器eax
mov DWORD PTR [eax+], OFFSET ??_8Left@@7B@;将??_8Left@@7B@所代表的的内存地址(即vbtable的首地址),写入到偏移类Left对象首地址4byte处
mov ecx, DWORD PTR _ii$[ebp];获取参数ii的值,给ecx寄存器
push ecx;压栈ecx,为调用虚基类构造函数传递参数
mov ecx, DWORD PTR _this$[ebp];获取类Left的首地址,给寄存器ecx
add ecx, ; 将类Left的首地址加上16byte,得到虚基类的首地址,作为隐含参数传递给虚基类的构造函数
call ??0Top@@QAE@H@Z ; 调用虚基类的构造函数
$LN1@Left:
mov edx, DWORD PTR _this$[ebp];获取类Left对象的首地址,给寄存器edx
mov DWORD PTR [edx], OFFSET ??_7Left@@6B0@@;将??_7Left@@6B0@@所代表的内存地址(即vftable首地址)写入类Left对象首地址处
mov eax, DWORD PTR _this$[ebp];获取类Left对象的首地址,给寄存器eax
mov ecx, DWORD PTR [eax+];获取偏移类Left对象首地址4byte处内存内容(即vbtable首地址)给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移vbtable首地址4byte处内存的内容(即虚基类虚表指针偏移vbtable指针的偏移量),给edx
mov eax, DWORD PTR _this$[ebp];获取类Left对象的首地址,给eax寄存器
mov DWORD PTR [eax+edx+], OFFSET ??_7Left@@6BTop@@@;eax寄存器里面存放有类Left对象的首地址,edx存放着虚基类虚表指针偏移vbtable指针的偏移量
;两者相加,在加上类Left对象自身的vptr大小4byte,得到虚基类Top的首地址
;将??_7Left@@6BTop@@@所带表的内存地址(即vftable首地址)写入到虚基类的首地址处
mov ecx, DWORD PTR _this$[ebp];获取类Left对象的首地址,给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移类Left对象的首地址4byte处的内容(即vbtable首地址),给寄存器edx
mov eax, DWORD PTR [edx+];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移vbtable指针的偏移量),给寄存器eax
sub eax, ; eax的值减12,通过调试汇编,或者下面的内存布局可以算出,其值为0
mov ecx, DWORD PTR _this$[ebp];获取类Left对象的首地址,给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移类Left对象的首地址4byte处的内容(即vbtable首地址),给寄存器edx
mov ecx, DWORD PTR [edx+];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移vbtable指针的偏移量),给寄存器ecx
mov edx, DWORD PTR _this$[ebp];获取类Left对象的首地址,给寄存器edx
mov DWORD PTR [edx+ecx], eax;edx寄存器保存这类Left对象的首地址,ecx寄存器保存着虚基类Top虚表指针偏移vbtable指针的偏移量,
;二者相加,得到vtordisp所在内存(关于vtordisp,参看文章下面提供的链接)
;将eax的值写入该内存 ; 17 : j = jj; mov eax, DWORD PTR _this$[ebp];获取类Left对象的首地址,给寄存器eax
mov ecx, DWORD PTR _jj$[ebp];将参数jj的值给寄存器ecx
mov DWORD PTR [eax+], ecx;将参数jj的值写入偏移类Left对象首地址8byte处内存,即给成员变量j赋值 ; 18 : } mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret ; 0000000cH
??0Left@@QAE@HH@Z ENDP

下面是Right构造函数的汇编码:

??0Right@@QAE@HH@Z PROC                    ; Right::Right, COMDAT
; _this$ = ecx ; 30 : Right(int kk, int ii) : Top(ii) { push ebp
mov ebp, esp
push ecx;压栈ecx的目的,是为了保存类Right对象的首地址预留空间
mov DWORD PTR _this$[ebp], ecx;ecx保存着类Right对象的首地址,存到刚才分配的空间里面
cmp DWORD PTR _$initVBases$[ebp], ;_$initVBases所带表的内存存放着调用Right构造函数之前传入的标志,这里将标志与0比较
je SHORT $LN1@Right;如果标志等于0,跳转到标号$LN1@Right执行,不调用虚基类的构造函数,否则,顺序执行。这里跳转到标号执行
mov eax, DWORD PTR _this$[ebp];获取类Right对象的首地址,给寄存器eax
mov DWORD PTR [eax+], OFFSET ??_8Right@@7B@;将??_8Right@@7B@所代表的内存地址(即vbtable首地址)写入到偏移类Right对象首地址4byte处内存
mov ecx, DWORD PTR _ii$[ebp];获取参数ii,给寄存器ecx
push ecx;压栈ecx,为调用虚基类构造函数传递参数
mov ecx, DWORD PTR _this$[ebp];获取类Right对象的首地址给寄存器ecx
add ecx, ; 将类Right对象的首地址加16,得到虚基类的首地址,存到寄存器ecx,作为隐含参数传递给虚基类构造函数
call ??0Top@@QAE@H@Z ; 调用虚基类的构造函数
$LN1@Right:
mov edx, DWORD PTR _this$[ebp];获取类Right对象的首地址给寄存器edx
mov DWORD PTR [edx], OFFSET ??_7Right@@6B0@@;将??_7Right@@6B0@@所代表的内存地址(即vftable的首地址)写入到类Right对象的首地址处
mov eax, DWORD PTR _this$[ebp];获取类Right对象的首地址给寄存器eax
mov ecx, DWORD PTR [eax+];获取偏移类Right对象首地址4byte处内存内容(即vbtable首地址),给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移vbtable首地址4byte处内存内容(即虚基类虚表指针偏移vbtable指针的偏移量),给寄存器edx
mov eax, DWORD PTR _this$[ebp];获取类Right对象的首地址给eax寄存器
mov DWORD PTR [eax+edx+], OFFSET ??_7Right@@6BTop@@@;eax寄存器保存类Right对象首地址,edx寄存器保存虚基类虚表指针偏移vbtable指针的偏移量
;二者相加,再加上类Right对象自身的vptr大小(4byte),得到虚基类的首地址
;将??_7Right@@6BTop@@@所带表的内存地址(即vftable首地址)写入到虚基类对象的首地址处
mov ecx, DWORD PTR _this$[ebp];获取类Right对象的首地址给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移类Right对象首地址4byte处内存内容(即vbtable首地址),给寄存器edx
mov eax, DWORD PTR [edx+];获取偏移vbtable首地址4byte处内存内容(即虚基类虚表指针偏移vbtable指针的偏移量),给寄存器eax
sub eax, ; eax的值减12,通过汇编调试或者内存布局可以发现,其值为0
mov ecx, DWORD PTR _this$[ebp];获取类Right对象的首地址给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移类Right对象首地址4byte处内存内容(即vbtable首地址),给寄存器edx
mov ecx, DWORD PTR [edx+];获取偏移vbtable首地址4byte处内存内容(即虚基类虚表指针偏移vbtable指针的偏移量),给寄存器ecx
mov edx, DWORD PTR _this$[ebp];获取类Right对象的首地址,给寄存器edx
mov DWORD PTR [edx+ecx], eax;寄存器edx里面保存着类Right的首地址,ecx寄存器保存着虚基类虚表指针偏移vbtable指针的偏移量
;两者相加,得到vtordisp的内存地址(关于vtrodisp参看文章后面的额链接) ; 31 : k = kk; mov eax, DWORD PTR _this$[ebp];获取类Right对象的首地址,给寄存器eax
mov ecx, DWORD PTR _kk$[ebp];获取参数kk的值,给寄存器ecx
mov DWORD PTR [eax+], ecx;将参数的值写入到偏移类Right对象首地址8byte处,即为成员变量k赋值 ; 32 : } mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret ; 0000000cH
??0Right@@QAE@HH@Z ENDP ; Right::Right

下面是Top构造函数的汇编码:

??0Top@@QAE@H@Z PROC                    ; Top::Top, COMDAT
; _this$ = ecx ; 5 : Top(int ii) { push ebp
mov ebp, esp
push ecx;压栈的目的是为了保存虚基类对象的首地址预留空间
mov DWORD PTR _this$[ebp], ecx;ecx里面存放虚基类对象的首地址,存放到刚才分配的空间
mov eax, DWORD PTR _this$[ebp];获取虚基类对象的首地址,给寄存器eax
mov DWORD PTR [eax], OFFSET ??_7Top@@6B@;将??_7Top@@6B@所代表的的内存地址,即vftale首地址,写入虚基类对象首地址处 ; 6 : i = ii; mov ecx, DWORD PTR _this$[ebp];获取虚基类对象首地址的值,给寄存器ecx
mov edx, DWORD PTR _ii$[ebp];获取参数ii的值,给寄存器edx
mov DWORD PTR [ecx+], edx;将参数ii的值写入偏移虚基类对象首地址4byte处,即给成员变量i赋值 ; 7 : } mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret
??0Top@@QAE@H@Z ENDP ; Top::Top

下面是类的继承关系图:

下面是4个类的内存布局:

关于Left Right 和Bottom中的vtordisp,请参看链接《关于vtrodisp知多少》

上面的内存布局也看以通过cmd命令行来查看,查看方法请参考链接《c++中如何查看一个类的内存布局》。有了它,对于其他类型的虚继承以及任何类的内存布局,都可以自行分析。例如:查看Bottom的内存布局,结果如下:

从Bottom的内存布局我们可以发现,其对象含有2个vbtable指针,3个vptr指针。其中第一个vptr指针为类Bottom对象和父类Left子对象共享,其vftable存储的是类Bottom和类Left的虚函数地址(不包括继承自虚基类);而第二个vptr指针指向的vftable存储的是类Right的虚函数地址(不包括继承自虚函数)。第三个vptr指向的vtable存储的是虚基类自己的虚函数。有了这些指针,就可以合理的运用多态,调用虚函数。

从main函数的汇编码中,我们还可以发现,当调用虚函数,作为隐含参数传递的this指针(由ecx传递)并不都是实际的对象首地址,而传递的是当前被调用虚函数原来所在类的首地址。比如main函数里面的这一段汇编码:

; 76   :     int btop = bp->getTop();

    mov    ecx, DWORD PTR _bp$[ebp];获取对象b的首地址给寄存器ecx
mov edx, DWORD PTR [ecx+];获取偏移对象首地址4字节处的内存内容(即vbtable首地址)给寄存器edx
mov eax, DWORD PTR [edx+];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移对象b的vbtable指针的偏移量),写入寄存器eax
mov ecx, DWORD PTR _bp$[ebp];获取对象b的首地址ecx
mov edx, DWORD PTR [ecx+];获取偏移对象首地址4字节处的内存内容(即vbtable首地址)给寄存器edx
mov ecx, DWORD PTR [edx+];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移对象b的vbtable指针的偏移量),写入寄存器ecx
mov edx, DWORD PTR _bp$[ebp];获取对象b首地址给寄存器edx
lea ecx, DWORD PTR [edx+ecx+];edx里面保存有对象b的首地址,ecx里面保存虚基类Top虚表指针到对象b的vbtable指针的偏移量
;二者相加,再加上对象b自身的vptr指针大小4byte,得到虚基类Top首地址,存放到寄存器ecx
mov edx, DWORD PTR _bp$[ebp];获取对象b的首地址,给寄存器edx
mov eax, DWORD PTR [edx+eax+];edx里面保存有对象b的首地址,eax里面保存虚基类Top虚表指针到对象b的vbtable指针的偏移量
;二者相加,再加上对象b自身的vptr指针大小4byte,得到虚基类Top首地址,存放到寄存器eax
mov edx, DWORD PTR [eax];获取虚基类首地址处内容(即vftable首地址)给寄存器eax,即虚函数getTop首地址
call edx;调用虚函数getTop
mov DWORD PTR _btop$[ebp], eax;eax保存函数getTop调用的返回结果,存放到btop变量

bp调用虚函数,而传递的隐含参数(this指针,由ecx寄存器传递,也就是上面助记符为lea所在代码)是虚基类Top对象的首地址。所以,当在getTop函数里面需要操作this指针时,都会有一个this指针的转换操作,使this指针指向调用当前虚函数的实际对象首地址。这个转换的值就是上面用cmd查看Bottom内存布局时最后4行显示的结果,它们存储在相应的虚表里,从上面的查看结果也可以看到。

从汇编看c++中的虚拟继承及内存布局(二)的更多相关文章

  1. C++中派生类对象的内存布局

    主要从三个方面来讲: 1 单一继承 2 多重继承 3 虚拟继承 1 单一继承 (1)派生类完全拥有基类的内存布局,并保证其完整性. 派生类可以看作是完整的基类的Object再加上派生类自己的Objec ...

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

    在从汇编看c++中指向成员变量的指针(一)中讨论的情形没有虚拟继承,下面来看看,当加入了虚拟继承的时候,指向成员变量的指针有什么变化. 下面是c++源码: #include <iostream& ...

  3. 转载:C++ 多继承和虚继承的内存布局

    C++ 多继承和虚继承的内存布局[已翻译100%] 英文原文:Memory Layout for Multiple and Virtual Inheritance 标签: <无> run_ ...

  4. 虚继承之单继承的内存布局(VC在编译时会把vfptr放到类的头部,这和Delphi完全一致)

    C++2.0以后全面支持虚函数与虚继承,这两个特性的引入为C++增强了不少功能,也引入了不少烦恼.虚函数与虚继承有哪些特性,今天就不记录了,如果能搞了解一下编译器是如何实现虚函数和虚继承,它们在类的内 ...

  5. 关于C++中的虚拟继承的一些总结

    1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念.虚拟基类是为解决多重继承而出现的.如:类D继承自类B1.B2,而类B1.B2都继承自类A,因此在类D中两次出现类A中的变量和函数.为了节省内存 ...

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

    在c++中,指向类成员变量的指针存储的并不是该成员变量所在内存的地址,而仅仅是该成员变量在该类对象中相对于对象首地址的偏移量.因此,它必须绑定到某一个对象或者对象指针上面,这里的对象和对象指针,就相当 ...

  7. 从汇编看c++中含有虚基类对象的析构

    c++中,当继承结构中含有虚基类时,在构造对象时编译器会通过将一个标志位置1(表示调用虚基类构造函数),或者置0(表示不调用虚基类构造函数)来防止重复构造虚基类子对象.如下图菱形结构所示: 当构造类B ...

  8. C++ 多继承和虚继承的内存布局(转)

    转自:http://www.oschina.net/translate/cpp-virtual-inheritance 警告. 本文有点技术难度,需要读者了解C++和一些汇编语言知识. 在本文中,我们 ...

  9. 浅谈Java虚拟机内存中的对象创建,内存布局,访问定位

    参考于 深入理解Java虚拟机 这里介绍HotSpot虚拟机(自带的虚拟机) 1.对象的创建 对于程序员来说,创建对象的方法: User user1 = new User(); User user2 ...

随机推荐

  1. OpenCV——KNN分类算法 <摘>

    KNN近邻分类法(k-Nearest Neighbor)是一个理论上比较成熟的方法,也是最简单的机器学习算法之一. 这个算法首先贮藏所有的训练样本,然后通过分析(包括选举,计算加权和等方式)一个新样本 ...

  2. [转载]OpenSUSE 13.2/13.1 安装搜狗拼音输入法

    1. 添加 M17N 源: 13.2: sudo zypper ar -f http://download.opensuse.org/repositories/M17N/openSUSE_13.2/ ...

  3. (原)ubuntu16在torch中使用caffe训练好的模型

    转载请注明出处: http://www.cnblogs.com/darkknightzh/p/5783006.html 之前使用的是torch,由于其他人在caffe上面预训练了inception模型 ...

  4. How to Use Javascript to Control Quicktime

    参看下面链接: How to Use Javascript to Control Quicktime;

  5. JDK动态代理例子

    JDK动态代理的代理类必须实现于接口.如果要代理类,则使用CGLIB代理. 先定义一个接口: public interface Character { public void show(); } 接着 ...

  6. 【solr专题之四】关于VelocityResponseWriter

    一.关于Velocity的基本配置 在Solr中,可以以多种方式返回搜索结果,如单纯的文本回复(XML.JSON.CSV等),也可以返回velocity,js等格式.而VelocityResponse ...

  7. javascript中写不写$(function() {});的区别

    原地址 $(document).ready() 里的代码是在页面内容都加载完才执行的,如果把代码直接写到script标签里,当页面加载完这个script标签就会执行里边的代码了,此时如果你标签里执行的 ...

  8. SQL实现递归及存储过程中In()参数传递解决方案[转]

    SQL实现递归及存储过程中In()参数传递解决方案   1.SQL递归 在SQL Server中,我们可以利用表表达式来实现递归算法,一般用于阻止机构的加载及相关性处理.   -->实现: 假设 ...

  9. git merge 分支

    把master merge到apple_campus1.git stash2.git checkout master3.git pull4.git checkout apple_campus5.git ...

  10. syslinux 3.84 2009-12-18 ebios copyright (c) 1994-2009 H. Peter Anvin et al

    使用USB方式安装win7+Ubuntu12.04双系统时出现"syslinux 3.84 2009-12-18 ebios copyright (c) 1994-2009 H. Peter ...