漫谈析构函数(一)——从一个面试题开始

在开始我们的内容前,首先让我们看一道面试题,题目如下:

说出下段代码的输出:

class A
{
public:
virtual void g()
{
cout<<"A::g()"<<endl;
}
private:
virtual void f()
{
cout<<"A::f()"<<endl;
}
};

class B:public A
{
public:
virtual void g()
{
cout<<"B::g()"<<endl;
}
private:
virtual void h()
{
cout<<"B::h()"<<endl;
}

};

typedef void(*Fun)(void);

int _tmain(int argc, _TCHAR* argv[])
{
B b;
Fun pFun;
for(int i=0;i<3;i++)
{
pFun=(Fun)*((int*)*(int*)(&b)+i);
pFun();
}
};

思考几分钟,看一下程序输出:

 

    如果你有疑问,或者不理解,我们就开彻底分析下这个程序。首先简单讨论一下虚函数在内存中与Class的关系。

4.1 虚函数的维护

带有一个虚函数的Class,以下3个操作会在编译期间发生:

1. 一个虚函数表(vtbl)会被编译器产生出来,内部存放Class的虚函数地址。(虚函数表是以Class维护的)

2. 每一个Class Object中,一个额外的虚函数表指针(vptr)会被编译器合成出来,内含vtbl的地址。(vptr是以Class Object维护的)

3. 对虚函数的调用会被改写,比如b.f(),(f()为虚函数)会被改写成:(*b.vptr[1])(&b),其中1表示f()在vtbl中的索引,&b代表调用f()的this指针。(可以看出虚函数的调用是需要以间接调用完成,效率相对普通成员函数要低)

此外带有一个虚函数的Class的默认合成构造函数以及拷贝构造函数不再是trivial,他需要为每个Object的vptr设定初值,使其指向适当的vtbl。

扩展:同理当一个Class直接或间接继承一个virtual base Class时,也不再表现“位逐次拷贝语义”,默认构造函数和拷贝构造函数也不再是trivial,因为需要正确的设置virtual base Class的偏移。

注:如图:两个B的对象之间或两个D的对象之间调用拷贝构造,“位逐次拷贝”不会发生问题,但是用D的对象给B的对象赋值(此时造成切割)就必须调整vptr和virtual base Class偏移。

4.2 虚函数表中的内容

那么虚函数表中的“虚函数”都包括哪些呢?总共有以下三类:

(1) 被当前Class覆盖(override)的base Class中的虚函数

(2) 继承自base Class的没有被override的虚函数。

(3) 纯虚函数(当前类定义的,或者从base Class继承来的)

4.3 虚函数的布局

    说了这么多,我们基本可以推断出程序中Class A,Class B的内存布局情况,如下图。注:Type_info以后讨论,这里先忽略。

  

      (a)Class A内存布局                           (b)Class B 内存布局

从上图可以看出A,B的对象中没有数据成员,只有一个vptr,这个我们可以输出验证:

cout<<sizeof(A)<<"  "<<sizeof(B)<<endl;(4,4)。

注:虚函数在vtbl中的顺序和虚函数在Class中的声明顺序一致。

4.4  pFun=(Fun)*((int*)*(int*)(&b)+i);

    相信不少人对程序中这条语句都有迷惑,下面我们来逐步分析下这条语句的含义。

首先,由程序中typedef语句我们知道Fun是一个函数指针,也就是这句话是要将某个地址转换成函数地址(指针)。具体步骤如下:

(1) (Fun)*((int*)*(int*)(&b)+i)----------------------------取对象b的地址,由对象的内存布局我们知道对象b的地址和vptr的地址相同,即取vptr的地址。

(2) (Fun)*((int*)*(int*)(&b)+i)----------------------------将vptr的地址强转成int型地址(指针),即让编译器将vptr的地址当做int型指针对待。否则*(&b)会被当做对象b。

(3) (Fun)*((int*)*(int*)(&b)+i)----------------------------得到vptr地址中存放的内容,即vptr(或者说vptr的值,或者说vtbl的地址)。

(4) (Fun)*((int*)*(int*)(&b)+i)----------------------------将vtbl的地址转为int指针,为之后与整型类型i相加做准备。

(5) (Fun)*((int*)*(int*)(&b)+i)---------------------------- 我们知道指针与整数的相加,移动的是指针所指对象的大小,因为上一步已经转换为int型指针,所以会移动i*sizeof(int)个字节。

(6) (Fun)*((int*)*(int*)(&b)+i)-----------------------------其实上一步就是将指针移动到vtbl中相应表条目处(存放虚函数的地址),所以这里取出地址的内容就是对应虚函数的地址。

(7) (Fun)*((int*)*(int*)(&b)+i)------------------------------最后这一步将函数的地址转换成函数指针,以后后面调用。

综上,我们可以知道这句活就是根据i的递增逐个调用B中的虚函数。而B中的虚函数布局我们已经知道,所以输出就不难理解了。

注:通过这个例子我们还发现了一个问题,函数f()在基类A中是私有的,而我们却访问到了。其实我们将B中的h()声明为private,输出结果依然不变,并不会引起访问权限问题。也就是说可以外界访问到Class的私有虚函数。这是为什么呢?我个人的理解是:访问限定符只在编译检查时候起作用,而在程序执行期间没有作用,因为从C++的函数名称修饰规则来看,并没有将访问限定符纳入其中,所以我们只要通过了编译,找到对应的函数地址就能够调用私有函数,因为在内存中私有函数和公有函数并没有什么区别。

 

 

时间: 2024-10-18 13:11:55

漫谈析构函数(一)——从一个面试题开始的相关文章

千万级-高并发WEB设计问题,来自一个面试题

问题描述 高并发WEB设计问题,来自一个面试题 这是一个面试题,困扰我好长时间了. 有个网站首页,需要满足千万级小数据量用户访问,首页上包含如下几部分: 1 统计部分,全站统计,与具体用户无关,与已存储的数据有关 2 静态页面部分 3 个人统计部分,与当前登录用户用惯,与已存储的数据有关,个人统计数据很少 4 数据部分,与具体内容无关,与已存储的数据有关.数据很少 要求: 1 满足千万级用户访问 2 前端可以负载,可以集群,可以异步 3 后端存储可以是DB,可以是内存,也可以是其他 4 技术 架

一个面试题引发的血案

  今天去奥博的天(objectival)面试遇到了这样的 一道的机试题       题干大致如下:              有关税率的问题: 一般的商品要交的消费税,单除了 医药,书籍,食品之外 不交税,他的税率是10%, 还有的就是 进口税 ,进口税 税率是5%,  .              测试用例通过xml来存放数据,             要求 本面试题完全使用了面向对象的方式来实现. 来计算商品的税收,并且商品税收是进行舍入0.05的操作,如13.14舍入是13.15, 60

请教一个面试题

问题描述 一个碗里有n根面条,每次从碗里拿出面条的两个端点(不一定是同一根面条的两个端点),把端点粘起来,直到没有端点了,问最后碗里环的个数的期望值.这个题目该怎么算??这题目是在这里看到的: 解决方案 解决方案二:[img=http://www.ifeng.com/][/img]解决方案三: 解决方案四:[img=bbbbbbbbbbbbbbbbbbbbbb][/img]解决方案五:这些纠结的面试题下次再给你做直接说我没吃过面条

请教一个面试题,据说是太保的。

问题描述 [问题]一台10G内存的服务器如何解析30G的文件,同时把文件里面不同的数据统计出来. 解决方案 解决方案二:该回复于2013-12-24 21:15:55被版主删除解决方案三:缓存到硬盘.解决方案四:引用2楼u010684923的回复: 缓存到硬盘. 没拉?解决方案五:应该是不一次性载入整个文件吧,一次只读取文件中的一部分读下一部分的时候关闭之前打开的部分.不过具体实现不太清楚解决方案六:每次读取固定大小解决方案七:引用3楼u011128470的回复: Quote: 引用2楼u010

【SQL 学习】一个面试题

今天面试的时候,问了一个sql编写的题目, 求每门成绩的最高分数的id 以及科目,分数.(当时没有写好,郁闷了,面试官还是很好的,给我讲解了一下!)回到宿舍自己有写了一下,两个方法: SQL> create table test (id int ,subject varchar2(20), score int); 表已创建. SQL> insert into test values(1 ,'math',95); 已创建 1 行. SQL> insert into test values(

一个面试题,挺好玩的,XDJM们来试试

问题描述 用if实现循环的效果.最少给出两种不同的实现. 解决方案 解决方案二:...递归解决方案三:很期待大牛们写出来看看~~~我想了半天就是想不出来!!!解决方案四:publicclassIfLoop{inti=0;intj=5;publicvoidtest(){if(i<j){System.out.println("i'svalueis:"+i++);test();}}publicstaticvoidmain(String[]args){newIfLoop().test()

一个面试题,关于服务器配置的,大家来看一下

问题描述 在linux上安装了3个tomcat,端口分别是8877,8888,8899,请问怎样把他们与3个域名绑定 解决方案 解决方案二:这个问题是挺麻烦的应该在apache里httpd.conf里面设置,我也记不太清楚了解决方案三:用apache做虚拟主机解决方案四:关注--

又一个面试题

9个点,用10条直线连,每条直线上最少连3个点,画画,我没做出来,9个点的位置随你定 嘿嘿,明天把答案发出来!终于想出来了

网上考试系统编制中的随机抽取试题的四种算法

算法|随机 因为教学的需要,我决定编写一个asp+ms sql2000的网上考试系统,其功能主要为:实现判断题.单项多项选择题和填空题的在线自动答题.改卷:并将学生的错误答案记入数据库,供教师分析.在编写从题库中随机抽取试题这一模块的算法上,却颇费了一番周折,现将解决过程记录如下,以供大家参考. 为了便于说明问题,文中提供的代码中的变量pd为从题库中要抽取出来考试的试题数量,数据库表名与字段名我都使用了中文,并仅以判断题为例. 算法一 由于不知道如何实现从题库中随机抽取试题的sql语句,我在网上