在来看看C++的虚函数和继承
1.C++代码
#include "iostream"
using namespace std;
class C
{
public:
int c;
virtual void display(int s);
};
void C::display(int s)
{
c = 3;
cout<<"this is in C:"<<s<<" "<<c<<endl;
}
class D : public C
{
public:
int d;
void display(int s);
};
void D::display(int s)
{
d = 4;
cout<<"this is in d:"<<s<<" "<<d<<endl;
}
void main()
{
C c;
c.display(sizeof(c));
C *d = (C *)new D;
d->display(sizeof(d));
}
2.汇编代码
1.debug编译
.text:00401830 main proc near ; CODE XREF: _mainj
.text:00401830
.text:00401830 var_54 = dword ptr -54h
.text:00401830 var_14 = dword ptr -14h
.text:00401830 var_8 = dword ptr -8
.text:00401830
.text:00401830 push ebp
.text:00401831 mov ebp, esp
.text:00401833 sub esp, 54h
.text:00401836 push ebx
.text:00401837 push esi
.text:00401838 push edi
.text:00401839 lea edi, [ebp+var_54]
.text:0040183C mov ecx, 15h
.text:00401841 mov eax, 0CCCCCCCCh
.text:00401846 rep stosd
.text:00401848 lea ecx, [ebp+var_8]
.text:0040184B call j_C__C
.text:00401850 push 8
.text:00401852 lea ecx, [ebp+var_8]
.text:00401855 call j_C__display
.text:0040185A lea ecx, [ebp+var_14]
.text:0040185D call j_D__D
.text:00401862 push 0Ch
.text:00401864 lea ecx, [ebp+var_14]
.text:00401867 call j_D__display
.text:0040186C pop edi
.text:0040186D pop esi
.text:0040186E pop ebx
.text:0040186F add esp, 54h
.text:00401872 cmp ebp, esp
.text:00401874 call __chkesp
.text:00401879 mov esp, ebp
.text:0040187B pop ebp
.text:0040187C retn
.text:0040187C main endp
在call真正的函数之前有一个call j_C__C,看看它的代码
.text:00401890 ; Attributes: bp-based frame
.text:00401890
.text:00401890 C__C proc near ; CODE XREF: j_C__Cj
.text:00401890
.text:00401890 var_44 = dword ptr -44h
.text:00401890 var_4 = dword ptr -4
.text:00401890
.text:00401890 push ebp
.text:00401891 mov ebp, esp
.text:00401893 sub esp, 44h
.text:00401896 push ebx
.text:00401897 push esi
.text:00401898 push edi
.text:00401899 push ecx
.text:0040189A lea edi, [ebp+var_44]
.text:0040189D mov ecx, 11h
.text:004018A2 mov eax, 0CCCCCCCCh
.text:004018A7 rep stosd
.text:004018A9 pop ecx
.text:004018AA mov [ebp+var_4], ecx
.text:004018AD mov eax, [ebp+var_4]
.text:004018B0 mov dword ptr [eax], offset ??_7C@@6B@ ; const C::`vftable'
.text:004018B6 mov eax, [ebp+var_4]
.text:004018B9 pop edi
.text:004018BA pop esi
.text:004018BB pop ebx
.text:004018BC mov esp, ebp
.text:004018BE pop ebp
.text:004018BF retn
.text:004018BF C__C endp
原来是获取,虚函数表的(*^__^*)...嘻嘻
2.release编译
.text:00401140 ; int __cdecl main(int argc,const char **argv,const char *envp)
.text:00401140 _main proc near ; CODE XREF: start+AFp
.text:00401140
.text:00401140 var_14 = dword ptr -14h
.text:00401140 var_C = dword ptr -0Ch
.text:00401140 argc = dword ptr 4
.text:00401140 argv = dword ptr 8
.text:00401140 envp = dword ptr 0Ch
.text:00401140
.text:00401140 sub esp, 14h
.text:00401143 push 8
.text:00401145 lea ecx, [esp+18h+var_14]
.text:00401149 mov [esp+18h+var_14], offset off_4120EC
.text:00401151 call sub_401000
.text:00401156 push 0Ch
.text:00401158 lea ecx, [esp+18h+var_C]
.text:0040115C mov [esp+18h+var_C], offset off_4120E8
.text:00401164 call sub_4010A0
.text:00401169 add esp, 14h
.text:0040116C retn
.text:0040116C _main endp
release就直接把虚函数表给解释出来了
3.输出结果
this is in C:8 3
this is in d:12 4
4.结论
1.虚基类除了变量还有4字节的vftable(在变量前面)
2.debug要用函数解释vftable,release直接给出
3.经常说的所谓虚函数被覆盖过程,可以看看class D解释vftable代码
.text:004018E9 pop ecx
.text:004018EA mov [ebp+var_4], ecx
.text:004018ED mov ecx, [ebp+var_4]
.text:004018F0 call j_C__C //初始化父类
.text:004018F5 mov eax, [ebp+var_4]
.text:004018F8 mov dword ptr [eax], offset ??_7D@@6B@ ; const D::`vftable'
.text:004018FE mov eax, [ebp+var_4] // 将写自己的表
从而可见,VC的编译器在编译的时候,没有覆盖的概念,只是编译的时候根据有需要的将虚函数表生成不同的几个而已。是哪个就用哪个表。
note:
虽然new出来的是一个子类对象,但是由于它付给了一个父类的类型,所以只能引用父类的成员。这就出现了一个奇怪的现象,在继承的时候已经将父类的成员继承到了子类的对象里面,而用vc查看的时候会发现这一点,奇怪的是由于上面的原因他将把子类的成员忽略掉。即在本来应该是子类成员的地方,还是父类成员的名称。虽然查看内存,已经发现子类的成员确实存在。而我们可以把这个看作是类型被没有warning的缩小了!
posted on 2008-03-02 22:12
margin 阅读(778)
评论(3) 编辑 收藏 引用 所属分类:
C/C++ 、
逆向工程