懂虚函数机制的来

lghe001 2016-11-14 07:21:31

class Base {
public:
Base(){cout <<"Base::Base" << endl; }
virtual void f() { cout <<"Base::f" << endl; }
virtual ~Base(){ cout <<"Base::~Base" << endl;}
int a;
};

class Drive:public Base
{
public:
Drive(){cout <<"Drive::Drive" << endl; }
virtual void f() { cout <<"Drive::f" << endl; }
~Drive(){ cout <<"Drive::~Drive" << endl;}
};

typedef void (*Fun)(void);
typedef void (Base::*Fun2)(void);
typedef void (Drive::*Fun3)(void);

int fun(Base b)
{
b.f();
return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
Drive d;
d.a = 5;

fun(d);

Base* pbase = &((Base)d);

printf("d:%p\t pbase:%p\n",&d, pbase);
printf("d.a:%p\t pbase->a:%p\n",d.a, pbase->a);
printf("&d.a:%p\t &pbase->a:%p\n",&d.a, &pbase->a);

pbase->f();

Fun2 pFun2 = &Base::f;
(pbase->*pFun2)();
printf("&Base::f:%p\n",pFun2);

Fun pFun = (Fun)*((int*)*(int*)(pbase));
pFun();
printf("虚表第一项地址内容:%p\n\n",pFun);

pbase = &(d);
printf("d:%p\t pbase:%p\n",&d, pbase);
printf("d.a:%p\t pbase->a:%p\n",d.a, pbase->a);
printf("&d.a:%p\t &pbase->a:%p\n",&d.a, &pbase->a);

pbase->f();

Fun3 pFun3 = &Drive::f;
((Drive *)pbase->*pFun3)();
printf("&Drive::f:%p\n",pFun3);


pFun = (Fun)*((int*)*(int*)(pbase));
pFun();
printf("虚表第一项地址内容:%p\n\n",pFun);

return 0;
}

vs2008平台运行结果:
Base::Base
Drive::Drive //d的构造过程

Base::f
Base::~Base //离开函数fun作用域,形参对象析构

Base::~Base //Base* pbase = &((Base)d);

d:0048F9F8 pbase:0048EFA0
d.a:00000005 pbase->a:00000005
&d.a:0048F9FC &pbase->a:0048EFA4

Base::f //pbase->f(),没问题
Base::f //(pbase->*pFun2)();通过成员函数指针调用
&Base::f:002A150A
Base::f //pFun();
虚表第一项地址内容:002A1389 //为什么不是002A150A?但是实际都是调用Base的f,Base的成员函数f难道有两份实现?

d:0048F9F8 pbase:0048F9F8
d.a:00000005 pbase->a:00000005
&d.a:0048F9FC &pbase->a:0048F9FC
Drive::f
Drive::f
&Drive::f:002A150A
Drive::f
虚表第一项地址内容:002A151E

疑惑如下:
1,fun(d);函数是传值方式传参数,那么应该生成一个Base的临时对象并用d来初始化,那么应该打印Base类的构造函数呀,但是实际却没有打印
2,Base* pbase = &((Base)d); 这里不是把d截断么?printf("d:%p\t pbase:%p\n",&d, pbase)打印的是:d:0048F9F8 pbase:0048EFA0,可以看得出是另外生成了Base对象而不是截断,为什么实际是打印了base的析构函数,而且只有析构没有构造!如果是在另外一块内存生成一个Base对象,并用d里面的Base部分来初始化它,为什么没有调Base的构造函数,而且析构函数来得也奇怪。
3,通过成员函数指针拿到的Base的f函数的地址pFun2和通过虚表拿到的Base的f函数的地址pFun为什么不一样?按道理编译器不可能对Base的成员函数f生成两份实现的呀
...全文
517 16 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
16 条回复
切换为时间正序
请发表友善的回复…
发表回复
FD_2013 2016-11-16
  • 打赏
  • 举报
回复
虚函数: 1.一个实现另一个没实现,调实现的那个。 2.两个都实现了,按继承顺序调。
sdghchj 2016-11-15
  • 打赏
  • 举报
回复
更准确点说,强制转换是先到转换类型的所有构造函数中去找,如果找不到对应的,再到被转换类型的所有operator里去找对应的。 3.(Fun)*((int*)*(int*)(pbase));这种方式是Derive::f的真实地址;而&Derive::f得到的不是真实的函数地址,因为虚函数在编译时是不知道具体地址的,只能在运行时才能确定,所以&Derive::f返回的一般是基于虚表起始地址的偏移量(g++就是),不同编译器返回的东西可能不同,msvc返回的可能(猜测)是虚表起始地址(也即是对象起始地址)+偏移量
Saleayas 2016-11-15
  • 打赏
  • 举报
回复
虚函数其实就是可以从基类引用呼叫的,但是在子类中实现的函数。 此时,在函数调用的时候,传入的是基类的 this 指针 ThisB。 但是在函数实现的时候,函数的 this 指针却是子类的 ThisD。 这两个指针可能一致,也可能不一致,比如多重继承。 此时需要进行 this 指针转换。 所以当虚函数被继承重写之后,需要一个类似于代理的函数 B,这个函数很简单, 就是调整 this 的偏移量,然后呼叫真正的重写函数 A。 所以,虚表里面的函数地址,其实是 B 函数,而 B 函数会呼叫 A 函数。 如果直接以 C++ 的类实例呼叫,此时会直接呼叫 A。 如果以实例的引用呼叫,此时就进入多态模式,呼叫虚函数 B,B函数调整 this,呼叫 A。
lghe001 2016-11-15
  • 打赏
  • 举报
回复
我以为强制转换会像基本类型的那样,只是对原来的内存按强制转换后的类型来重新解释而已.OK,1,2算是搞明白了
sdghchj 2016-11-15
  • 打赏
  • 举报
回复
上面说错,强制转换是先找Base类的拷贝构造函数,如果找不到对应的,再去找Derive的operator Base()
sdghchj 2016-11-15
  • 打赏
  • 举报
回复
1.调用的是编译器自动生成的拷贝构造函数,不是你写的构造函数。 2.Base* pbase = &((Base)d);强制转换是调用编译器给Derive自动生成的operator Base()即Base操作符 (而不是调用Base的构造函数),生成临时对象,对临时对象右值取地址是不符合标准的(只是默认情况下VS不会报错,而g++会报错),临时对象马上析构,后面的pbase的所有使用都是未定义行为,即使调用了它的f()因没涉及到成员变量所以没有发生崩溃。
fefe82 2016-11-15
  • 打赏
  • 举报
回复
引用 5 楼 lghe001 的回复:
[quote=引用 2 楼 fefe82 的回复:] 1.2. 用的拷贝构造函数。没有写的话编译器会自动生成一个默认的。
印象中,子类的新添加的成员变量内存布局时只是加在父类的后面,所以把子类对象强制转为父类对象,应该是对子类对象进行了截断而已,也就是重新按父类类型来解释原来子类对象的内存不是么?[/quote] “新添加的成员变量内存布局时只是加在父类的后面” 这个有编译器决定。 “子类对象强制转为父类对象” 这个会生成一个新对象。(使用拷贝构造函数,拷贝构造函数的参数是你所说的“截断”的对象) 强制转换基本都会生成一个新的(临时)对象。 “父类类型来解释原来子类对象的内存” 这个也与编译器、继承关系(是否有虚函数,是否单继承,是否虚继承 .....)都有关系。不过最终还是编译器说了算。 =============== 子类的指针转换为父类的指针,子类的对象绑定到父类的引用,通常不生成新对象。 父类的应用绑定到子类的对象时,是直接绑定到父类子对象的。
lghe001 2016-11-15
  • 打赏
  • 举报
回复
引用 4 楼 Saleayas 的回复:
C++ 的虚函数的实现由两个函数。 一个是虚表的实现函数A,一个是 C++ 类成员的实现函数B。 B 在对 this 指针转换之后 再呼叫 A。 比如 class C { virtual void F(); }; C c; c.F(); 呼叫的是 B。 (&c)->F(); 呼叫的是 A;
意思是B其实并不是真正的F()代码实现的地址?B里面通过调用A才跳转到F()的代码实现?
lghe001 2016-11-15
  • 打赏
  • 举报
回复
引用 2 楼 fefe82 的回复:
1.2. 用的拷贝构造函数。没有写的话编译器会自动生成一个默认的。
印象中,子类的新添加的成员变量内存布局时只是加在父类的后面,所以把子类对象强制转为父类对象,应该是对子类对象进行了截断而已,也就是重新按父类类型来解释原来子类对象的内存不是么?
ArthurJava 2016-11-15
  • 打赏
  • 举报
回复
把构造函数写全就明白,不是一个对象
fefe82 2016-11-15
  • 打赏
  • 举报
回复
引用 9 楼 sdghchj 的回复:
上面说错,强制转换是先找Base类的拷贝构造函数,如果找不到对应的,再去找Derive的operator Base()
这个时候只会使用构造函数。 operator Base() 最多被用来提供 copy constructor 的参数。如果 copy constructor 不存在(系统默认生成的也不存在,或无法调用(比如 private)),只有 operator Base() 是没有用的。
赵4老师 2016-11-15
  • 打赏
  • 举报
回复
《深度探索C++对象模型》 《C++反汇编与逆向分析技术揭秘》
Saleayas 2016-11-14
  • 打赏
  • 举报
回复
C++ 的虚函数的实现由两个函数。 一个是虚表的实现函数A,一个是 C++ 类成员的实现函数B。 B 在对 this 指针转换之后 再呼叫 A。 比如 class C { virtual void F(); }; C c; c.F(); 呼叫的是 B。 (&c)->F(); 呼叫的是 A;
ID870177103 2016-11-14
  • 打赏
  • 举报
回复
2楼正解 3.虚成员函数的地址得到的其实是虚表的偏移(某种格式),不是真正的函数地址
fefe82 2016-11-14
  • 打赏
  • 举报
回复
1.2. 用的拷贝构造函数。没有写的话编译器会自动生成一个默认的。
yshuise 2016-11-14
  • 打赏
  • 举报
回复
class Base { public: Base(){cout <<"Base::Base" << endl; } virtual void f() { cout <<"Base::f" << endl; } virtual ~Base(){ cout <<"Base::~Base" << endl;} int a; }; ==================== 最好纯虚类
[14本经典Android开发教程] 8 Linux内核阅读心得体会 读核感悟 2 读核感悟 Linux内核启动 内核的生成 2 读核感悟 Linux内核启动 从hello world说起 3 读核感悟 Linux内核启动 BIOS 5 读核感悟 Linux内核启动 setup辅助程序 6 读核感悟 Linux内核启动 内核解压缩 8 读核感悟 Linux内核启动 开启页面映射 9 读核感悟 Linux内核启动 链接脚本 11 读核感悟 伪装现场 系统调用参数 13 读核感悟 伪装现场 fork 系统调用 15 读核感悟 伪装现场 内核线程: 17 读核感悟 伪装现场 信号通信 19 读核感悟 kbuild系统 内核模块的编译 22 读核感悟 kbuild系统 编译到内核和编译成模块的区别 24 读核感悟 kbuild系统 make bzImage的过程 26 读核感悟 kbuild系统 make menuconfig 31 读核感悟 文件系统 用C来实现面向对象 32 读核感悟 设计模式 用C来实现虚函数表和多态 32 读核感悟 设计模式 用C来实现继承和模板 33 读核感悟 设计模式 文件系统和设备的继承和接口 34 读核感悟 设计模式 文件系统与抽象工厂 36 读核感悟 阅读源代码技巧 查找定义 37 读核感悟 阅读源代码技巧 变量命名规则 42 读核感悟 内存管理 内核中的页表映射总结 43 读核感悟 健壮的代码 exception table 内核中的刑事档案 44 读核感悟 定时器 巧妙的定时器算法 45 读核感悟 内存管理 page fault处理流程 45 读核感悟 文件读写 select实现原理 47 读核感悟 文件读写 poll的实现原理 49 1 功能介绍: 49 2 关键的结构体: 49 3 poll的实现 49 4 性能分析: 50 读核感悟 文件读写 epoll的实现原理 50 1 功能介绍 50 2 关键结构体: 51 3 epoll create的实现 53 4 epoll ctl的实现 53 5 epoll wait的实现 54 6 性能分析 54 读核感悟 同步问题 同步问题概述 55 1 同步问题的产生背景 55 2 内核态与用户态的区别 55 读核感悟 同步问题 内核态自旋锁的实现 56 1自旋锁的总述 56 2非抢占式的自旋锁 56 3 锁的释放 57 4 与用户态的自旋锁的比较 57 5 总结 58 读核感悟 内存管理 free命令详解 58 读核感悟 文件读写 2 6 9内核中的AIO 59 1 AIO概述 59 2 内核态AIO的使用 61 读核感悟 文件读写 内核态AIO相关结构体 61 1 内核态AIO操作相关信息 61 2 AIO上下文: 63 3 AIO ring 63 4 异步I O事件的返回信息 64 读核感悟 文件读写 内核态AIO创建和提交操作 65 1 AIO上下文的创建 io setup 65 2 AIO请求的提交:io submit实现机制 66 读核感悟 文件操作 AIO操作的执行 66 1 在提交时执行AIO 66 2 在工作队列中执行AIO 66 3 负责AIO执行的核心函数aio run iocb 67 4 AIO操作的完成 67 读核感悟 文件读写 内核态是否支持非direct I O方式的AIO 67 已上传7本: [14本经典Android开发教程] 1 Android开发从入门到精通 http: download csdn net detail cleopard 8355245 [14本经典Android开发教程] 2 Android开发手册 API函数详解 http: download csdn net detail cleopard 8374487 [14本经典Android开发教程] 3 Android SDK 中文开发文档 http: download csdn net detail cleopard 8380429 [14本经典Android开发教程] 4 Android应用程序开发36技 http: download csdn net detail cleopard 8380495 [14本经典Android开发教程] 5 linux Android基础知识总结 http: download csdn net detail cleopard 8380529 [14本经典Android开发教程] 6 Android驱动开发入门及手机案例开发分析教程 http: download csdn net detail cleopard 8388019 [14本经典Android开发教程] 7 Android编程入门教程 http: download csdn net detail cleopard 8388043 剩余8本稍后上传 @或直接从这里寻找@ http: download csdn net user cleopard album @更多@ http: cleopard download csdn net 福利 http: xuemeilaile com 17份软件测试文档 http: download csdn net album detail 1425 13份WPF经典开发教程 http: download csdn net album detail 1115 C#资料合辑二[C#桌面编程入门篇] http: download csdn net album detail 957 C#资料合辑一[C#入门篇] http: download csdn net album detail 669 [Csharp高级编程 第6版 ] 共8压缩卷 http: download csdn net album detail 667 10个[精品资源]Java学习资料合辑[一] http: download csdn net album detail 663 10个C#Socket编程代码示例 http: download csdn net album detail 631 6份GDI+程序设计资源整合[全零分] http: download csdn net album detail 625 2014年移动游戏行业数据分析 http: download csdn net detail cleopard 8340331 一文读2014年全球互联网广告新生态 http: download csdn net detail cleopard 8340303">[14本经典Android开发教程] 8 Linux内核阅读心得体会 读核感悟 2 读核感悟 Linux内核启动 内核的生成 2 读核 [更多]

65,199

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

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