从汇编看c++中的虚拟持续及内存布局(二)
添加时间:2013-6-22 点击量:
下面是c++源码:
class Top {//虚基类
public:
int i;
Top(int ii) {
i = ii;
}
virtual int getTop() {
cout << (long)this << endl;
return 1;
}
};
class Left : public virtual Top {
public:
int j;
Left(int jj, int ii) : Top(ii) {
j = jj;
}
int getTop() {
return 2;
}
virtual int getLeft() {
return 1;
}
};
class Right : public virtual Top {
public:
int k;
Right(int kk, int ii) : Top(ii) {
k = kk;
}
int getTop() {
return 3;
}
virtual int getRight() {
return 1;
}
};
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 4;
}
int getLeft() {
return 2;
}
int getRight() {
return 2;
}
virtual int getBottom() {
return 1;
}
};
int main() {
Bottom b(1, 2, 3, 4);
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, 84 ; 为法度中的变量预保存储空间,此中对象b占用40byte
; 61 : Bottom b(1, 2, 3, 4);
push 1;压入标记,1默示调用虚基类机关函数 0默示不调用虚基类机关函数
push 4;压栈4,为对象b机关函数传递参数
push 3;压栈3,为对象b机关函数传递参数
push 2;压栈2,为对象b机关函数传递参数
push 1;压栈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+4];获取偏移父类对象Left首地址4byte处内存内容(即vbtable的首地址)给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top对象的虚表指针偏移父类Left对象vbtable指针的偏移量),给存放器edx
mov eax, DWORD PTR _lp¥[ebp];获父类对象Left的首地址给存放器eax
mov ecx, DWORD PTR [eax+4];获取偏移父类对象Left首地址4byte处内存内容(即vbtable的首地址)给存放器ecx
mov eax, DWORD PTR [ecx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top对象的虚表指针偏移父类Left对象vbtable指针的偏移量),给存放器eax
mov ecx, DWORD PTR _lp¥[ebp];获取父类对象Left的首地址给存放器ecx
lea ecx, DWORD PTR [ecx+eax+4];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+4];存放器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], 0;斗劲对象b首地址是否为0,即断定bp指针是否为空
je SHORT ¥LN3@main;若是bp指针为空,就会跳转到标号¥LN3@main处履行,不然次序履行 这里次序履行
mov ecx, DWORD PTR _bp¥[ebp];获取对象b的首地址给存放器ecx
add ecx, 12 ; 将对象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], 0;若是指针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+4];获取偏移父类Right对象首地址4byte处内存内容(即vbtable首地址)给存放器edx
mov eax, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top的虚表指针偏移vbtable指针的偏移量),给存放器eax
mov ecx, DWORD PTR _rp¥[ebp];获取父类Right对象的首地址给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移父类Right对象首地址4byte处内存内容(即vbtable首地址)给存放器edx
mov ecx, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top的虚表指针偏移vbtable指针的偏移量),给存放器ecx
mov edx, DWORD PTR _rp¥[ebp];获取父类Right对象的首地址给存放器edx
lea ecx, DWORD PTR [edx+ecx+4];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+4];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], 0;斗劲bp指针是否为0,即断定bp指针是否为空
jne SHORT ¥LN5@main;若是为空,就次序履行,不然,就跳转到标号¥LN5@main处履行 这里跳转到标号履行
mov DWORD PTR tv170[ebp], 0;将0赋给姑且变量tv170
jmp SHORT ¥LN6@main;跳转到标号¥LN6@main履行
¥LN5@main:
mov eax, DWORD PTR _bp¥[ebp];将对象b首地址给存放器eax
mov ecx, DWORD PTR [eax+4];获取偏移对象b首地址4byte处内存内容(即vbtable首地址)给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移vbtable首地址4byte处内存内容,即虚基类Top虚表指针到vbtable指针的偏移量,给存放器edx
mov eax, DWORD PTR _bp¥[ebp];将对象b的首地址给存放器eax
lea ecx, DWORD PTR [eax+edx+4];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];获取偏移对象首地址4字节处的内存内容(即vbtable首地址)给存放器edx
mov eax, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移对象b的vbtable指针的偏移量),写入存放器eax
mov ecx, DWORD PTR _bp¥[ebp];获取对象b的首地址ecx
mov edx, DWORD PTR [ecx+4];获取偏移对象首地址4字节处的内存内容(即vbtable首地址)给存放器edx
mov ecx, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移对象b的vbtable指针的偏移量),写入存放器ecx
mov edx, DWORD PTR _bp¥[ebp];获取对象b首地址给存放器edx
lea ecx, DWORD PTR [edx+ecx+4];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+4];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, 12 ; 将对象b的首地址加上12,即获得父类Right对象首地址,保存到存放器ecx
mov edx, DWORD PTR _bp¥[ebp];获取对象b的首地址,给存放器edx
mov eax, DWORD PTR [edx+12];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+4];获取偏移虚表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 0
_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], 0;_¥initVBases所代表的内存存放着调用Bottom机关函数之前压入的标记,这里将标记与0进行斗劲
je SHORT ¥LN1@Bottom;斗劲成果为0,即标记为0,就跳转到标号¥LN1@Bottom处履行,避免反复调用虚基类机关函数 不然次序履行 这里次序履行
mov eax, DWORD PTR _this¥[ebp];将对象b的首地址给存放器eax
mov DWORD PTR [eax+4], OFFSET ??_8Bottom@@7BLeft@@@;将??_8Bottom@@7BLeft@@@所代表的内存地址(即vbtable首地址)写入偏移对象
;首地址4byte处内存,即赋值vbtable指针
mov ecx, DWORD PTR _this¥[ebp];将对象首地址给存放器ecx
mov DWORD PTR [ecx+16], 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, 32 ; 对象b的首地址加上32byte,获得虚基类Top首地址,存放到存放器ecx,作为隐含参数传递给虚基类机关函数
call ??0Top@@QAE@H@Z ; 调用虚基类机关函数
¥LN1@Bottom:
push 0;压入标记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;压入标记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, 12 ; 给对象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+12], OFFSET ??_7Bottom@@6BRight@@@;将??_7Bottom@@6BRight@@@所代表的内存首地址(vftable首地址)写入偏移对象b首地址12byte处
;即写到了父类Right对象的首地址处
mov eax, DWORD PTR _this¥[ebp];获取对象b的首地址,给存放器eax
mov ecx, DWORD PTR [eax+4];获取偏移对象b首地址4byte处内存内容(即vbtable首地址)给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top的虚表指针偏移对象b的vbtable指针的偏移量),给存放器edx
mov eax, DWORD PTR _this¥[ebp];获取对象b的首地址给存放器eax
mov DWORD PTR [eax+edx+4], 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+4];获取偏移对象首地址4byte处内存内容(即vbtable首地址),给存放器edx
mov eax, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内容,即获取虚基类Top虚表指针偏移对象b的vbtable指针的偏移量,给存放器eax
sub eax, 28 ; 将上述获取的偏移量减28
mov ecx, DWORD PTR _this¥[ebp];获取对象b的首地址,给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移对象首地址4byte处内存内容(即vbtable首地址),给存放器edx
mov ecx, DWORD PTR [edx+4];获取偏移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+24], ecx;将参数ll的值写入偏移对象b首地址24byte处的内存,即为成员变量l赋值
; 46 : }
mov eax, DWORD PTR _this¥[ebp]
mov esp, ebp
pop ebp
ret 20 ; 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], 0;_¥initVBases所带表的内存存放着调用Left机关函数之前传入的标记
;这里将标记与0斗劲
je SHORT ¥LN1@Left;若是标记为0,则跳到标号¥LN1@Left处履行,不会调用虚基类的机关函数,不然,次序履行,这里跳到标号处履行
mov eax, DWORD PTR _this¥[ebp];获取类Left对象的首地址,给存放器eax
mov DWORD PTR [eax+4], 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, 16 ; 将类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+4];获取偏移类Left对象首地址4byte处内存内容(即vbtable首地址)给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移vbtable首地址4byte处内存的内容(即虚基类虚表指针偏移vbtable指针的偏移量),给edx
mov eax, DWORD PTR _this¥[ebp];获取类Left对象的首地址,给eax存放器
mov DWORD PTR [eax+edx+4], 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+4];获取偏移类Left对象的首地址4byte处的内容(即vbtable首地址),给存放器edx
mov eax, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移vbtable指针的偏移量),给存放器eax
sub eax, 12 ; eax的值减12,经由过程调试汇编,或者下面的内存布局可以算出,其值为0
mov ecx, DWORD PTR _this¥[ebp];获取类Left对象的首地址,给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移类Left对象的首地址4byte处的内容(即vbtable首地址),给存放器edx
mov ecx, DWORD PTR [edx+4];获取偏移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+8], ecx;将参数jj的值写入偏移类Left对象首地址8byte处内存,即给成员变量j赋值
; 18 : }
mov eax, DWORD PTR _this¥[ebp]
mov esp, ebp
pop ebp
ret 12 ; 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], 0;_¥initVBases所带表的内存存放着调用Right机关函数之前传入的标记,这里将标记与0斗劲
je SHORT ¥LN1@Right;若是标记便是0,跳转到标号¥LN1@Right履行,不调用虚基类的机关函数,不然,次序履行。这里跳转到标号履行
mov eax, DWORD PTR _this¥[ebp];获取类Right对象的首地址,给存放器eax
mov DWORD PTR [eax+4], 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, 16 ; 将类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+4];获取偏移类Right对象首地址4byte处内存内容(即vbtable首地址),给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类虚表指针偏移vbtable指针的偏移量),给存放器edx
mov eax, DWORD PTR _this¥[ebp];获取类Right对象的首地址给eax存放器
mov DWORD PTR [eax+edx+4], 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+4];获取偏移类Right对象首地址4byte处内存内容(即vbtable首地址),给存放器edx
mov eax, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类虚表指针偏移vbtable指针的偏移量),给存放器eax
sub eax, 12 ; eax的值减12,经由过程汇编调试或者内存布局可以发明,其值为0
mov ecx, DWORD PTR _this¥[ebp];获取类Right对象的首地址给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移类Right对象首地址4byte处内存内容(即vbtable首地址),给存放器edx
mov ecx, DWORD PTR [edx+4];获取偏移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+8], ecx;将参数的值写入到偏移类Right对象首地址8byte处,即为成员变量k赋值
; 32 : }
mov eax, DWORD PTR _this¥[ebp]
mov esp, ebp
pop ebp
ret 12 ; 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+4], edx;将参数ii的值写入偏移虚基类对象首地址4byte处,即给成员变量i赋值
; 7 : }
mov eax, DWORD PTR _this¥[ebp]
mov esp, ebp
pop ebp
ret 4
??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];获取偏移对象首地址4字节处的内存内容(即vbtable首地址)给存放器edx
mov eax, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移对象b的vbtable指针的偏移量),写入存放器eax
mov ecx, DWORD PTR _bp¥[ebp];获取对象b的首地址ecx
mov edx, DWORD PTR [ecx+4];获取偏移对象首地址4字节处的内存内容(即vbtable首地址)给存放器edx
mov ecx, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移对象b的vbtable指针的偏移量),写入存放器ecx
mov edx, DWORD PTR _bp¥[ebp];获取对象b首地址给存放器edx
lea ecx, DWORD PTR [edx+ecx+4];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+4];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++源码:
class Top {//虚基类
public:
int i;
Top(int ii) {
i = ii;
}
virtual int getTop() {
cout << (long)this << endl;
return 1;
}
};
class Left : public virtual Top {
public:
int j;
Left(int jj, int ii) : Top(ii) {
j = jj;
}
int getTop() {
return 2;
}
virtual int getLeft() {
return 1;
}
};
class Right : public virtual Top {
public:
int k;
Right(int kk, int ii) : Top(ii) {
k = kk;
}
int getTop() {
return 3;
}
virtual int getRight() {
return 1;
}
};
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 4;
}
int getLeft() {
return 2;
}
int getRight() {
return 2;
}
virtual int getBottom() {
return 1;
}
};
int main() {
Bottom b(1, 2, 3, 4);
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, 84 ; 为法度中的变量预保存储空间,此中对象b占用40byte
; 61 : Bottom b(1, 2, 3, 4);
push 1;压入标记,1默示调用虚基类机关函数 0默示不调用虚基类机关函数
push 4;压栈4,为对象b机关函数传递参数
push 3;压栈3,为对象b机关函数传递参数
push 2;压栈2,为对象b机关函数传递参数
push 1;压栈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+4];获取偏移父类对象Left首地址4byte处内存内容(即vbtable的首地址)给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top对象的虚表指针偏移父类Left对象vbtable指针的偏移量),给存放器edx
mov eax, DWORD PTR _lp¥[ebp];获父类对象Left的首地址给存放器eax
mov ecx, DWORD PTR [eax+4];获取偏移父类对象Left首地址4byte处内存内容(即vbtable的首地址)给存放器ecx
mov eax, DWORD PTR [ecx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top对象的虚表指针偏移父类Left对象vbtable指针的偏移量),给存放器eax
mov ecx, DWORD PTR _lp¥[ebp];获取父类对象Left的首地址给存放器ecx
lea ecx, DWORD PTR [ecx+eax+4];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+4];存放器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], 0;斗劲对象b首地址是否为0,即断定bp指针是否为空
je SHORT ¥LN3@main;若是bp指针为空,就会跳转到标号¥LN3@main处履行,不然次序履行 这里次序履行
mov ecx, DWORD PTR _bp¥[ebp];获取对象b的首地址给存放器ecx
add ecx, 12 ; 将对象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], 0;若是指针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+4];获取偏移父类Right对象首地址4byte处内存内容(即vbtable首地址)给存放器edx
mov eax, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top的虚表指针偏移vbtable指针的偏移量),给存放器eax
mov ecx, DWORD PTR _rp¥[ebp];获取父类Right对象的首地址给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移父类Right对象首地址4byte处内存内容(即vbtable首地址)给存放器edx
mov ecx, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top的虚表指针偏移vbtable指针的偏移量),给存放器ecx
mov edx, DWORD PTR _rp¥[ebp];获取父类Right对象的首地址给存放器edx
lea ecx, DWORD PTR [edx+ecx+4];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+4];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], 0;斗劲bp指针是否为0,即断定bp指针是否为空
jne SHORT ¥LN5@main;若是为空,就次序履行,不然,就跳转到标号¥LN5@main处履行 这里跳转到标号履行
mov DWORD PTR tv170[ebp], 0;将0赋给姑且变量tv170
jmp SHORT ¥LN6@main;跳转到标号¥LN6@main履行
¥LN5@main:
mov eax, DWORD PTR _bp¥[ebp];将对象b首地址给存放器eax
mov ecx, DWORD PTR [eax+4];获取偏移对象b首地址4byte处内存内容(即vbtable首地址)给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移vbtable首地址4byte处内存内容,即虚基类Top虚表指针到vbtable指针的偏移量,给存放器edx
mov eax, DWORD PTR _bp¥[ebp];将对象b的首地址给存放器eax
lea ecx, DWORD PTR [eax+edx+4];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];获取偏移对象首地址4字节处的内存内容(即vbtable首地址)给存放器edx
mov eax, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移对象b的vbtable指针的偏移量),写入存放器eax
mov ecx, DWORD PTR _bp¥[ebp];获取对象b的首地址ecx
mov edx, DWORD PTR [ecx+4];获取偏移对象首地址4字节处的内存内容(即vbtable首地址)给存放器edx
mov ecx, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移对象b的vbtable指针的偏移量),写入存放器ecx
mov edx, DWORD PTR _bp¥[ebp];获取对象b首地址给存放器edx
lea ecx, DWORD PTR [edx+ecx+4];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+4];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, 12 ; 将对象b的首地址加上12,即获得父类Right对象首地址,保存到存放器ecx
mov edx, DWORD PTR _bp¥[ebp];获取对象b的首地址,给存放器edx
mov eax, DWORD PTR [edx+12];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+4];获取偏移虚表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 0
_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], 0;_¥initVBases所代表的内存存放着调用Bottom机关函数之前压入的标记,这里将标记与0进行斗劲
je SHORT ¥LN1@Bottom;斗劲成果为0,即标记为0,就跳转到标号¥LN1@Bottom处履行,避免反复调用虚基类机关函数 不然次序履行 这里次序履行
mov eax, DWORD PTR _this¥[ebp];将对象b的首地址给存放器eax
mov DWORD PTR [eax+4], OFFSET ??_8Bottom@@7BLeft@@@;将??_8Bottom@@7BLeft@@@所代表的内存地址(即vbtable首地址)写入偏移对象
;首地址4byte处内存,即赋值vbtable指针
mov ecx, DWORD PTR _this¥[ebp];将对象首地址给存放器ecx
mov DWORD PTR [ecx+16], 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, 32 ; 对象b的首地址加上32byte,获得虚基类Top首地址,存放到存放器ecx,作为隐含参数传递给虚基类机关函数
call ??0Top@@QAE@H@Z ; 调用虚基类机关函数
¥LN1@Bottom:
push 0;压入标记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;压入标记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, 12 ; 给对象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+12], OFFSET ??_7Bottom@@6BRight@@@;将??_7Bottom@@6BRight@@@所代表的内存首地址(vftable首地址)写入偏移对象b首地址12byte处
;即写到了父类Right对象的首地址处
mov eax, DWORD PTR _this¥[ebp];获取对象b的首地址,给存放器eax
mov ecx, DWORD PTR [eax+4];获取偏移对象b首地址4byte处内存内容(即vbtable首地址)给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top的虚表指针偏移对象b的vbtable指针的偏移量),给存放器edx
mov eax, DWORD PTR _this¥[ebp];获取对象b的首地址给存放器eax
mov DWORD PTR [eax+edx+4], 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+4];获取偏移对象首地址4byte处内存内容(即vbtable首地址),给存放器edx
mov eax, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内容,即获取虚基类Top虚表指针偏移对象b的vbtable指针的偏移量,给存放器eax
sub eax, 28 ; 将上述获取的偏移量减28
mov ecx, DWORD PTR _this¥[ebp];获取对象b的首地址,给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移对象首地址4byte处内存内容(即vbtable首地址),给存放器edx
mov ecx, DWORD PTR [edx+4];获取偏移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+24], ecx;将参数ll的值写入偏移对象b首地址24byte处的内存,即为成员变量l赋值
; 46 : }
mov eax, DWORD PTR _this¥[ebp]
mov esp, ebp
pop ebp
ret 20 ; 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], 0;_¥initVBases所带表的内存存放着调用Left机关函数之前传入的标记
;这里将标记与0斗劲
je SHORT ¥LN1@Left;若是标记为0,则跳到标号¥LN1@Left处履行,不会调用虚基类的机关函数,不然,次序履行,这里跳到标号处履行
mov eax, DWORD PTR _this¥[ebp];获取类Left对象的首地址,给存放器eax
mov DWORD PTR [eax+4], 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, 16 ; 将类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+4];获取偏移类Left对象首地址4byte处内存内容(即vbtable首地址)给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移vbtable首地址4byte处内存的内容(即虚基类虚表指针偏移vbtable指针的偏移量),给edx
mov eax, DWORD PTR _this¥[ebp];获取类Left对象的首地址,给eax存放器
mov DWORD PTR [eax+edx+4], 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+4];获取偏移类Left对象的首地址4byte处的内容(即vbtable首地址),给存放器edx
mov eax, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移vbtable指针的偏移量),给存放器eax
sub eax, 12 ; eax的值减12,经由过程调试汇编,或者下面的内存布局可以算出,其值为0
mov ecx, DWORD PTR _this¥[ebp];获取类Left对象的首地址,给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移类Left对象的首地址4byte处的内容(即vbtable首地址),给存放器edx
mov ecx, DWORD PTR [edx+4];获取偏移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+8], ecx;将参数jj的值写入偏移类Left对象首地址8byte处内存,即给成员变量j赋值
; 18 : }
mov eax, DWORD PTR _this¥[ebp]
mov esp, ebp
pop ebp
ret 12 ; 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], 0;_¥initVBases所带表的内存存放着调用Right机关函数之前传入的标记,这里将标记与0斗劲
je SHORT ¥LN1@Right;若是标记便是0,跳转到标号¥LN1@Right履行,不调用虚基类的机关函数,不然,次序履行。这里跳转到标号履行
mov eax, DWORD PTR _this¥[ebp];获取类Right对象的首地址,给存放器eax
mov DWORD PTR [eax+4], 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, 16 ; 将类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+4];获取偏移类Right对象首地址4byte处内存内容(即vbtable首地址),给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类虚表指针偏移vbtable指针的偏移量),给存放器edx
mov eax, DWORD PTR _this¥[ebp];获取类Right对象的首地址给eax存放器
mov DWORD PTR [eax+edx+4], 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+4];获取偏移类Right对象首地址4byte处内存内容(即vbtable首地址),给存放器edx
mov eax, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类虚表指针偏移vbtable指针的偏移量),给存放器eax
sub eax, 12 ; eax的值减12,经由过程汇编调试或者内存布局可以发明,其值为0
mov ecx, DWORD PTR _this¥[ebp];获取类Right对象的首地址给存放器ecx
mov edx, DWORD PTR [ecx+4];获取偏移类Right对象首地址4byte处内存内容(即vbtable首地址),给存放器edx
mov ecx, DWORD PTR [edx+4];获取偏移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+8], ecx;将参数的值写入到偏移类Right对象首地址8byte处,即为成员变量k赋值
; 32 : }
mov eax, DWORD PTR _this¥[ebp]
mov esp, ebp
pop ebp
ret 12 ; 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+4], edx;将参数ii的值写入偏移虚基类对象首地址4byte处,即给成员变量i赋值
; 7 : }
mov eax, DWORD PTR _this¥[ebp]
mov esp, ebp
pop ebp
ret 4
??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];获取偏移对象首地址4字节处的内存内容(即vbtable首地址)给存放器edx
mov eax, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移对象b的vbtable指针的偏移量),写入存放器eax
mov ecx, DWORD PTR _bp¥[ebp];获取对象b的首地址ecx
mov edx, DWORD PTR [ecx+4];获取偏移对象首地址4字节处的内存内容(即vbtable首地址)给存放器edx
mov ecx, DWORD PTR [edx+4];获取偏移vbtable首地址4byte处内存内容(即虚基类Top虚表指针偏移对象b的vbtable指针的偏移量),写入存放器ecx
mov edx, DWORD PTR _bp¥[ebp];获取对象b首地址给存放器edx
lea ecx, DWORD PTR [edx+ecx+4];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+4];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行显示的成果,它们存储在响应的虚表里,从上方的查算作果也可以看到。
容易发怒的意思就是: 别人做了蠢事, 然后我们代替他们, 表现出笨蛋的样子。—— 蔡康永