让你更了解多态---关于虚函数表的一点总结

heiheizh618 2009-08-26 04:37:36
我在前些天发过《关于MFC消息映射表与虚函数的效率问题》这个帖子,得到了很多朋友的指点,同时我也发现很多比我更新的编程新手对于其中关于虚函数表的实现 一知半解,所以我觉得还是把我自己的 新手心得 发出来,让更多的新接触编程的朋友了解虚函数的实现。 如有错误,敬请指正。


虚函数表是针对类设计的,每个类的所有对象都共享一份虚函数表,每个对象中都有一个 指针成员变量 指向这个 类类型 的虚函数表。
我 猜测 这个虚函数表应该是类的静态成员,且为private不允许修改。

在VS中做个小实验就可以得出来。大家请耐心看下去。
1.设计带有虚函数的基类和重写了这个虚函数的派生类。
2.定义对象,并初始化,在初始化后的任意语句设置断点,启动调试
3.在“局部窗口”里面,点开 局部对象 左边的加号,里面第一个成员就是虚函数表,这是编译器为我们加上去的。
4.而这个虚函数表不是整个链表类型,在最后一列的类型中可以明确看到 这是个指针类型
5.在第二栏里面可以看到 这个指针存储的值


#include<iostream>
using namespace std;

class Base
{
public:
void virtual func1()
{
cout<<"Base function1"<<endl;
}
void virtual func2()
{
cout<<"Base function2"<<endl;
}
};

class Derive:public Base
{
public:
void virtual func1()
{
cout<<"Derive function1"<<endl;
}
};

int main()
{
Base *pObj=new Derive();
return 0;
}




根据以上步骤打开虚函数表,我想大家就会明白了,虚函数表里面存储的是这样的数据.
__vfptr指向const Derive::'vftable'
而Derive::'vftable'里面又是怎么存储的呢?
vftable[0]=Derive::func1(void)
vftable[1]=Base::func2(void)

所以,其实所有的相同类的对象享有的都是同样的虚函数表。
他们的多态行为,其实也是一成不变的---在他们的类类型定义完之后。
通过作用域限定符,就可以明确指出这个类型的对象到底应该调用哪个版本的虚函数了,仅仅加一个类作用域操作符而已。
...全文
1296 59 打赏 收藏 转发到动态 举报
写回复
用AI写文章
59 条回复
切换为时间正序
请发表友善的回复…
发表回复
laowang2 2009-08-29
  • 打赏
  • 举报
回复
梧桐168 2009-08-29
  • 打赏
  • 举报
回复
虚函数表不存在什么private属性,private属性是编译期行为,而虚函数表这块内存不可写
是运行期行为。
你可以用类似代码运行期修改虚函数表:
long** pplVrtable= (long**)(this);

HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ::GetCurrentProcessId());

MEMORY_BASIC_INFORMATION mbi = {0};

if (VirtualQueryEx(hProcess, (LPVOID)(*pplVrtable), &mbi, sizeof(mbi)) != sizeof(mbi))
return;

DWORD dwOldProtect = 0;
if(!::VirtualProtectEx(hProcess, mbi.BaseAddress, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect))
return;

(*pplVrtable)[0] = (*pplVrtable)[1];


DWORD dwTemp = 0;
::VirtualProtectEx(hProcess, mbi.BaseAddress, 4, dwOldProtect, &dwTemp);
CloseHandle(hProcess);

这就是COM HOOK的基本原理
sams_wang 2009-08-29
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 tttyd 的回复:]
不是静态的成员变量,只是普通的指针成员变量,每一个类对象都会有这样的一个指针.
[/Quote]
不是每个类都会有这个指针的,只有类或者父类中包含有虚函数的才会有这个指针,如果没有虚函数的话,就不会有这个指针,这样处理就可以上了4个字节的空间的,编译器的原则是不会无缘无故的增加一个不需要的指针的
sams_wang 2009-08-29
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 tttyd 的回复:]
虚函数表是针对类设计的,每个类的所有对象都共享一份虚函数表,每个对象中都有一个 指针成员变量 指向这个 类类型 的虚函数表。
我 猜测 这个虚函数表应该是类的静态成员,且为private不允许修改。

不是静态的成员变量,指示普通的指针成员变量,每一个类对象都会有这样的一个指针.
[/Quote]
这个指针一般都是编译器使用的,不对用户开放,但是严格来说这个指针及其指针所指向的vtbl的数据都是可以可以修改的,前提是你要知道类在内存中的存储布局,不同的编译器对C++类的内存布局是不同的,就我所知,VC编译器跟C++ BUILDER编译器对类的内存布局就是有所不同的
heiheizh618 2009-08-28
  • 打赏
  • 举报
回复
[Quote=引用 53 楼 dqy168888 的回复:]
程序结果是 Drive fun1 吗?
[/Quote]

跟程序结果没关系~
你在主函数return的地方设置断点,再按实验的步骤来做~
DavidQY 2009-08-28
  • 打赏
  • 举报
回复
程序结果是 Drive fun1 吗?
donil 2009-08-28
  • 打赏
  • 举报
回复
mark
wzyzb 2009-08-28
  • 打赏
  • 举报
回复
回帖 给分
naziace 2009-08-28
  • 打赏
  • 举报
回复
不错
sin816 2009-08-28
  • 打赏
  • 举报
回复
很不错 学习了
猞猁狲 2009-08-27
  • 打赏
  • 举报
回复
先顶再看
dronly 2009-08-27
  • 打赏
  • 举报
回复
很值得学习的文章。
荣誉帝王 2009-08-27
  • 打赏
  • 举报
回复
学习了
heiheizh618 2009-08-27
  • 打赏
  • 举报
回复
[Quote=引用 34 楼 yshuise 的回复:]
Derive::func1(void) ;
这种形式是存放的偏移量,而不是函数地址。所以用vtable来存放就不正确了。
[/Quote]
请教。。请说得详细点。。

我也只能按存放的函数地址来理解多态的实现,因为这样理解比较容易懂。。
yshuise 2009-08-27
  • 打赏
  • 举报
回复
Derive::func1(void) ;
这种形式是存放的偏移量,而不是函数地址。所以用vtable来存放就不正确了。
Tomzzu 2009-08-27
  • 打赏
  • 举报
回复
技术好贴
blingpro 2009-08-27
  • 打赏
  • 举报
回复
mark 有空再看
bluesky396 2009-08-27
  • 打赏
  • 举报
回复
学习...
zb_fly 2009-08-27
  • 打赏
  • 举报
回复
好贴,顶
fandh 2009-08-27
  • 打赏
  • 举报
回复
不错,不错!总结的帖子值得学习!
加载更多回复(37)

16,472

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧