无忧技术网 - RSS订阅 
无忧技术网

C++中的虚函数与虚函数表


作者:[佚名] - 发布:2010-4-22 17:35:38 - 来源:无忧技术网
学习 C++ 的同志不知道有没有和我一样遇到过这样的困惑:C++中的虚函数到底怎么实现的?在各种继承关系中,虚函数表的结构到底是什么样的?曾经我是很想当然,可是后来在使用ATL的过程中,我发现并不是我想的那样。大家知道,利用C++语言本身的特性进行COM编程当然是很方便的事,但是你就得随时随地都知道那虚函数表里头到底是些什么东西。讲C++语法的书没有义务告诉你C++产生的虚函数表是什么样的,这就是头痛的所在。
自已做试验是件很快乐的事,我很愿意这么做。

首先写个函数,作为我们实验的基础。传入虚函数表指针,显示虚数表的内容。

void DispVFT(DWORD* pVFT)
{

printf("VFT Pointer:%p\n" , pVFT);
printf("Begin\n");
DWORD* p = (DWORD* )*pVFT;//得到VFT的首址
while(*p) file://这/个地方我是看表项是不是为空来判断是否到了表尾,
file://大/多数情况都是对的,不过不能为准
{
printf("VF:%p , %p\n", p , *p);
p++;
}
printf("End\n\n");
}


首先我们看单个类时的虚函数表的情况:
class C1
{
public:
C1()
{
file://printf/("In C1\n");
file://DispVFT/((DWORD*)this);
};
virtual F1()
{}
};
void main()
{
C1 c1;

file://由/于C1中没有成员数据,所有我们可以用这种方式判断
file://C1/中的虚函数表指针的个数
printf("vftptr count :%d\n" , sizeof(C1) / 4);
file://显/示内存结构
DispVFT((DWORD* )&c1);
}

输出:
vftptr count :1
VFT Pointer:0012FF7C
Begin
VF:00420048 , 00401078(C1::F1)
End

很单纯,不用多讲,这是我们意料之中的结果。


下面我们进行简单继承的实验

class C2 : public C1
{
public:
C2(){
printf("In C1\n");
DispVFT((DWORD*)this);
}
virtual F2()
{}
};

void main()
{
C2 c2;
C1* pC1 = &c2;
printf("vftptr count :%d\n" , sizeof(C2) / 4);
printf("C1\n");
DispVFT((DWORD*)pC1);
printf("C2\n");
DispVFT((DWORD*)&c2);
}
输出:
In C1
VFT Pointer:0012FF7C
Begin
VF:00420048 , 00401087(C1::F1)
End

In C2
VFT Pointer:0012FF7C
Begin
VF:004200F4 , 00401087(C1::F1) file://输/出的第一项是表的首址与对应的表项内容,看看地址,与 In C1的不同,说明是不同的两个表
VF:004200F8 , 0040108C(C2::F2)
End

vftptr count :1
C1
VFT Pointer:0012FF7C
Begin
VF:004200F4 , 00401087(C1::F1)
VF:004200F8 , 0040108C(C2::F2)
End

C2
VFT Pointer:0012FF7C
Begin
VF:004200F4 , 00401087(C1::F1)
VF:004200F8 , 0040108C(C2::F2)
End

大家可以看到最后虚函数表指针仍然是同一个,表中按顺序放入了C1(基类)与C2(派生类)的虚函数指针。

下面是多重继承
class C1
{
public:
C1()
{
file://printf/("In C1\n");
file://DispVFT/((DWORD*)this);
}
virtual F1(){}
};

class C2
{
public:
C2(){
file://printf/("In C1\n");
file://DispVFT/((DWORD*)this);
}
virtual F2(){}
};
class C3 : public C1 , public C2
{
public:
C3(){
file://printf/("In C1\n");
file://DispVFT/((DWORD*)this);
}
virtual F3(){}
};

void main()
{
C3 c3;
C2* pC2 = &c3;
C1* pC1 = &c3;
printf("vftptr count :%d\n" , sizeof(C3) / 4);
printf("C1\n");
DispVFT((DWORD*)pC1);
printf("C2\n");
DispVFT((DWORD*)pC2);
printf("C3\n");
DispVFT((DWORD*)&c3);
}
输出:
vftptr count :2
C1
VFT Pointer:0012FF78
Begin
VF:00420104 , 00401046(C1::F1)
VF:00420108 , 0040101E(C3::F3)
End

C2
VFT Pointer:0012FF7C
Begin
VF:00420100 , 00401028(C2::F2)
VF:00420104 , 00401046(C1::F1)
VF:00420108 , 0040101E(C3::F3)
End

C3
VFT Pointer:0012FF78
Begin
VF:00420104 , 00401046(C1::F1)
VF:00420108 , 0040101E(C3::F3)
End

虚函数表指针变成两个了,也就是说现在是用两个虚函数表指针维护一个表,总结一下就是,虚函数表指针的个数等于基类的个数。至于虚函数表的个数,应该是三个。你可以把我在构造函数中加的代码去掉注释符,看看输出。你会发现每次输出的表的首地址都是不一样,那表当然也不是同一个表。


下面说说多层继承的情况:
class C1
{
public:
C1()
{
printf("In C1\n");
DispVFT((DWORD*)this);
}
virtual F1(){}
};

class C2 : public C1
{
public:
C2(){
printf("In C2\n");
DispVFT((DWORD*)this);
}
virtual F2(){}
};
class C3 : public C2
{
public:
C3(){
printf("In C3\n");
DispVFT((DWORD*)this);
}
virtual F3(){}
};


void main()
{
C3 c3;
C2* pC2 = &c3;
C1* pC1 = &c3;
printf("vftptr count :%d\n" , sizeof(C3) / 4);
printf("C1\n");
DispVFT((DWORD*)pC1);
printf("C2\n");
DispVFT((DWORD*)pC2);
printf("C3\n");
DispVFT((DWORD*)&c3);

}

输出:
In C1
VFT Pointer:0012FF7C
Begin
VF:00421090 , 00401046(C1::F1)
End

In C2
VFT Pointer:0012FF7C
Begin
VF:0042010C , 00401046(C1::F1) file://这/里是类C2的vftable,第一个输出是它首址与表项内容
VF:00420110 , 00401028(C2::F2)
End

In C3
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046(C1::F1)
VF:004210A0 , 00401028(C2::F2)
VF:004210A4 , 00401078(C3::F3)
End

vftptr count :1
C1
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

C2
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

C3
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

得到的结果:我们看到了虚函数表指针是一个,可是你仔细看看每个构造函数的输出!输出的第一项是表的首址与对应的表项。大家可以看到,首址都是不一样的这说明是三个不同的表,那么这个类就有三个虚函数表。你可能会想,这三个表在什么时候用呢,事实上,在C3的实例被构造出来后,只有最后一个表,也就是C3的表在用,其它的表跟本就是没有用的,C++在没有通过你同意的情况下,在浪费你的空间(多重继承也存在同样的问题)。微软想了个办法把其它的不用的虚函数表去掉:__declspec(novtable)


class __declspec(novtable)C1
{
public:
C1()
{
file://printf/("In C1\n");
file://DispVFT/((DWORD*)this); file://这/里得去掉,既然没有那个表,怎么输出
}
virtual F1(){}
};

class __declspec(novtable)C2 : public C1
{
public:
C2(){
file://printf/("In C2\n");
file://DispVFT/((DWORD*)this);//这里得去掉,既然没有那个表,怎么输出
}
virtual F2(){}
};
class C3 : public C2
{
public:
C3(){
printf("In C3\n");
DispVFT((DWORD*)this);
}
virtual F3(){}
};


void main()
{

C3 c3;
C2* pC2 = &c3;
C1* pC1 = &c3;
printf("vftptr count :%d\n" , sizeof(C3) / 4);
printf("C1\n");
DispVFT((DWORD*)pC1);
printf("C2\n");
DispVFT((DWORD*)pC2);
printf("C3\n");
DispVFT((DWORD*)&c3);

}

输出:
In C3
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

vftptr count :1
C1
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

C2
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

C3
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

可以看到一切正常,只是在不知不觉中,你的程序瘦身成功,不过如果你决定要去掉类的虚函数表,你最好可以确定这个类应该是个被继承的基类,而不是最后派生使用的类。否则可能会出错,比如:

void func1(C1* p)
{
p->F1();
}
void main()
{
C2 c2;
func1(&c2);
}

这种情况就会出错,没有虚函数表,虚函数的调用怎么能实现呢?


如果说得有什么不对,望同志们指正赐教。

责任编辑:liqwei
打印本页】【关闭本页】【返回列表
·上一篇:C++编程思想笔记之五
·下一篇:C源码:常用攻击程序
 文章评分
  • current rating
-5 -4 -3 -2 -1 0 +1 +2 +3 +4 +5
 相关文章
 相关评论
 站点最新文章 更多>> 
·[经典影音]弱点
·[经典影音]萨利机长
·[经典影音]天空之眼
·[管理知识]康奈尔笔记法,提高100%学习效率
·[管理知识]刘强东:我管75000人靠这4张表格
·[管理知识]跟壳牌学HSE管理
·[运营策划]编辑工作内容整理
·[至理名言]奋斗与决定
·[瀚海拾遗]盲人打灯笼之各家论道
·[搞笑段子]中国男足
 站点浏览最多 更多>> 
·[协议规范]http断点续传原理:http头 Range、…
·[JS/CSS/HTML]HTML 空格的表示符号 nbsp / en…
·[NoSQL]Mongo数据库简介
·[协议规范]什么是SPF记录?如何设置、检测SP…
·[协议规范]图解 HTTPS 通信过程
·[PHP]精选国外免费PHP空间推荐
·[程序综合]常用IP地址查询接口
·[程序综合]什么是 DNS Prefetch ?
·[程序综合]获取客户端IP地址的三个HTTP请求…
·[Linux]/usr 目录的由来