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),多态性是将接口与实现进行分离:用形象的语 ...
随机推荐
- 【u215】河床
问题描述 小明是一个地理学家,经常要对一段河流进行测量分析.他从上游开始向下游方向等距离地选择了N个点测量水位深度.得到一组数据d1,d2,--,dn,回到实验室后数据分析员根据需要对数据进行分析,发 ...
- Android 离线缓存的高速实现
离线缓存是指在有网络的状态下将从server获取的网络数据.如Json 数据缓存到本地,在断网的状态下启动APP时读取本地缓存数据显示在界面上,经常使用的APP(网易新闻.知乎等等)都是支持离线缓存的 ...
- Android 三种方式实现自定义圆形进度条ProgressBar
一.通过动画实现 定义res/anim/loading.xml如下: <?xml version="1.0" encoding="UTF-8"?> ...
- 【codeforces 754B】 Ilya and tic-tac-toe game
time limit per test2 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...
- erlang OTP gen_server 图解分析
http://www.hoterran.info/otp-gen_server-sourcecode 在阅读erlang的otp源码gen_server.erl的时候,一直想写点什么,用一种最好的方式 ...
- 学习鸟哥的Linux私房菜笔记(3)——基础使用
一.设备文件 设备在Linux中以特殊文件的形式存在 块(block)设备文件 字符(character)设备文件 设备文件所在位置 查看设备类型 二.虚拟控制台及用户身份切换 在系统中有12个虚拟控 ...
- lucene 统计单词次数(词频tf)并进行排序
public class WordCount { static Directory directory; // 创建分词器 static Analyzer analyzer = new IKAnaly ...
- Windows消息:WM_USER与WM_APP的区别
Windows消息范围及意义 #define WM_USER 0x0400 #define WM_APP 0x8000 0到WM_USER-1 Messages reserved for use by ...
- OpenCL基本概念
OpenCL程序同CUDA程序一样,也是分为两部分,一部分是在主机(以CPU为核心)上运行,一部分是在设备(以GPU为核心)上运行.在设备上运行的程序被称为核函数.但是对于核函数的编写,CUDA一般直 ...
- tinkerpop(1) 地图数据库console科研
原创文章连接: http://blog.csdn.net/freewebsys/article/details/46348975 转载请注明出处. 1,关于图数据库 tinkerpop是apache孵 ...