C++中构造函数能调用虚函数吗?(答案是语法可以,输出错误),但Java里居然可以
环境:XPSP3 VS2005
今天黑总给应聘者出了一个在C++的构造函数中调用虚函数的问题,具体的题目要比标题复杂,大体情况可以看如下的代码:
- class Base
- {
- public:
- Base()
- {
- Fuction();
- }
- virtual void Fuction()
- {
- cout << "Base::Fuction" << endl;
- }
- };
- class A : public Base
- {
- public:
- A()
- {
- Fuction();
- }
- virtual void Fuction()
- {
- cout << "A::Fuction" << endl;
- }
- };
- // 这样定义一个A的对象,会输出什么?
- A a;
首先回答标题的问题,调用当然是没有问题的,但是获得的是你想要的结果吗?或者说你想要什么样的结果?
有人说会输出:
- A::Fuction
- A::Fuction
如果是这样,首先我们回顾下C++对象模型里面的构造顺序,在构造一个子类对象的时候,首先会构造它的基类,如果有多层继承关系,实际上会从最顶层的基类逐层往下构造(虚继承、多重继承这里不讨论),如果是按照上面的情形进行输出的话,那就是说在构造Base的时候,也就是在Base的构造函数中调用Fuction的时候,调用了子类A的Fuction,而实际上A还没有开始构造,这样函数的行为就是完全不可预测的,因此显然不是这样,实际的输出结果是:
- Base::Fuction
- A::Fuction
据说在Java中是上一种输出(感觉有点匪夷所思)。
我们来单步看一下到底发生了什么?在A的构造函数里面首先会去调用Base的构造函数,Base的构造函数如下:
class Base
{
public:
Base()
00411600 push ebp
00411601 mov ebp,esp
00411603 sub esp,0CCh
00411609 push ebx
0041160A push esi
0041160B push edi
0041160C push ecx
0041160D lea edi,[ebp-0CCh]
00411613 mov ecx,33h
00411618 mov eax,0CCCCCCCCh
0041161D rep stos dword ptr es:[edi]
0041161F pop ecx
00411620 mov dword ptr [ebp-8],ecx
00411623 mov eax,dword ptr [this]
00411626 mov dword ptr [eax],offset Base::`vftable' (41770Ch)
{
Fuction();
0041162C mov ecx,dword ptr [this]
0041162F call Base::Fuction (4111A9h)
}
00411634 mov eax,dword ptr [this]
00411637 pop edi
00411638 pop esi
00411639 pop ebx
0041163A add esp,0CCh
00411640 cmp ebp,esp
00411642 call @ILT+460(__RTC_CheckEsp) (4111D1h)
00411647 mov esp,ebp
00411649 pop ebp
0041164A ret
从单步跟踪来看,注意黑色加粗的那部分汇编代码,ecx中存放的是对象的地址(0x0012ff60,我的机器上的情况看下图,有图有真相),首先是设置vtable的地址到对象的前四个字节(不同的编译器可能不同),然后就直接调用了Base::Fuction函数,并没有走虚机制,而我们此时看虚表中的状态,虚表已经填充的是0x4111a9,注意虚表的地址0x0041770c,而此时对象地址0x0012FF60前四个字节存放的正是0x0041770c。
继续跟踪,流程又回到A的构造函数中,再次注意加粗部分的代码,从基类Base的构造函数返回后,在A的构造函数中,重设了虚表指针,现在的虚表指针是(0x417700h),同样调用Fuction的时候直接调用了A::Fuction函数,并没有使用虚机制,而且此时虚表0x417700h指向的位置存放的0x41110e正是A::Fuction的地址。

class A : public Base
{
public:
A()
00411590 push ebp
00411591 mov ebp,esp
00411593 sub esp,0CCh
00411599 push ebx
0041159A push esi
0041159B push edi
0041159C push ecx
0041159D lea edi,[ebp-0CCh]
004115A3 mov ecx,33h
004115A8 mov eax,0CCCCCCCCh
004115AD rep stos dword ptr es:[edi]
004115AF pop ecx
004115B0 mov dword ptr [ebp-8],ecx
004115B3 mov ecx,dword ptr [this]
004115B6 call Base::Base (411140h)
004115BB mov eax,dword ptr [this]
004115BE mov dword ptr [eax],offset A::`vftable' (417700h)
{
Fuction();
004115C4 mov ecx,dword ptr [this]
004115C7 call A::Fuction (41110Eh)
}
004115CC mov eax,dword ptr [this]
004115CF pop edi
004115D0 pop esi
004115D1 pop ebx
004115D2 add esp,0CCh
004115D8 cmp ebp,esp
004115DA call @ILT+460(__RTC_CheckEsp) (4111D1h)
004115DF mov esp,ebp
004115E1 pop ebp
004115E2 ret
其实事情就是这么简单。
http://blog.csdn.net/magictong/article/details/6734241
C++中构造函数能调用虚函数吗?(答案是语法可以,输出错误),但Java里居然可以的更多相关文章
- 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数
关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...
- C++ Daily 《3》----构造函数可否是虚函数
C++ 中构造函数可否是虚函数? 绝不要!! 而且,在构造函数中调用虚函数也是不提倡的行为,因为会引发预想不到的结果. 因为,在 derived class 对象构造的过程中,首先调用的是基类的构造函 ...
- C++构造函数中不能调用虚函数
在构造函数中调用虚函数,并不会产生多态的效果,就跟普通函数一样. c++ primer 第四版中497页15.4.5构造函数和析构中的虚函数讲到,如果在构造函数或析构函数中调用虚函数,则运行的是为构造 ...
- 关于在C#中构造函数中调用虚函数的问题
在C#中如果存在类的继承关系,应避免在构造函数中调用虚函数.这是由于C#的运行机制造成的,原因如下: 新建一个类实例时,C#会先初始化该类(对类变量赋值,并将函数记在函数表中),然后再初始化父类.构造 ...
- C++ 构造函数中调用虚函数
我们知道:C++中的多态使得可以根据对象的真实类型(动态类型)调用不同的虚函数.这种调用都是对象已经构建完成的情况.那如果在构造函数中调用虚函数,会怎么样呢? 有这么一段代码: class A { p ...
- 【校招面试 之 C/C++】第10题 C++不在构造函数和析构函数中调用虚函数
1.不要在构造函数中调用虚函数的原因 在概念上,构造函数的工作是为对象进行初始化.在构造函数完成之前,被构造的对象被认为“未完全生成”.当创建某个派生类的对象时,如果在它的基类的构造函数中调用虚函数, ...
- 在构造函数和析构函数中调用虚函数------新标准c++程序设计
在构造函数和析构函数中调用虚函数不是多态,因为编译时即可确定调用的是哪个函数.如果本类有该函数,调用的就是本类的函数:如果本类没有,调用的就是直接基类的函数:如果基类没有,调用的就是间接基类的函数,以 ...
- EC笔记,第二部分:9.不在构造、析构函数中调用虚函数
9.不在构造.析构函数中调用虚函数 1.在构造函数和析构函数中调用虚函数会产生什么结果呢? #; } 上述程序会产生什么样的输出呢? 你一定会以为会输出: cls2 make cls2 delete ...
- C++中为什么构造函数不能是虚函数,析构函数是虚函数
一, 什么是虚函数? 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数.虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离:用形象的语 ...
随机推荐
- MySQL的表空间管理
表空间: MySQL没有真正意义上的表空间管理. MySQL的Innodb包含两种表空间文件模式,默认的共享表空间和每个表分离的独立表空间. 一般来说,当数据量很小的时候建议使用共享表空间的管理方式. ...
- RFC chinese
rfc专业性强,现实中不可能有好的全的rfc的翻译 尝试上在github上搜索 https://github.com/tidyjiang8/6lowpan-rfcs-chinese 诚如作者所说: 在 ...
- PatentTips -- 一种在CoAP网络中注册的方法及装置
技术领域 [0001] 本发明涉及一种在CoAP网络中注册的方法及装置,属于网络通信技术领域. 背景技术 [0002] (Internet of Things,物联网)作为新一代的信息技术,越来越受到 ...
- IT忍者神龟之Hibernat持久化对象-数据表映射配置回想
1.持久化对象POJO编写规则: 1) 有空參public构造器: 2) 提供标识属性.映射数据表主键: 3) 属性提供setter和getter方法. 4) 属性使用基本数据类型的包装类型.基本类型 ...
- OSGi开发环境的建立
1 OSGi开发环境的建立 1.1 Equinox是什么 从代码角度来看,Equinox其实就是OSGi核心标准的完整实现,并且还在这个基础上增加了一些额外的功能(比如为框架增加了命令行和程序执行的入 ...
- 【BZOJ 1018】 [SHOI2008]堵塞的交通traffic
[题目链接]:http://www.lydsy.com/JudgeOnline/problem.php?id=1018 [题意] [题解] 按照这里的题解写的http://blog.csdn.net/ ...
- 【codeforces 777D】Cloud of Hashtags
[题目链接]:http://codeforces.com/contest/777/problem/D [题意] 让你通过删除字符串的末尾字符的方式; 使得所给的字符串以字典序升序排序; 不能交换字符串 ...
- CSS:描述样式
一.CSS css:Cascading Style Sheet,层叠样式表,用于描述网页样式 1. 同一个标签可以有多个选择器作用,给他增加样式: 2. 有继承性,祖先的标签的一些属性,可以继承给后代 ...
- 西门子S7报文解析
1.报文的基本格式 1.1 第1和第2个字节是:固定报文头03 00,这里我们就用到三种报文: a.初始化 b. 读 c.写,都是这种格式: 1.2 第3和第4个字节是:整个报文的长度: 其它部分就是 ...
- quartz2.x源码分析——启动过程
title: quartz2.x源码分析--启动过程 date: 2017-04-13 14:59:01 categories: quartz tags: [quartz, 源码分析] --- 先简单 ...