深入理解C++对象模型-对象的内存布局,vptr,vtable

vtpr的位置:
       为了支持多态,C++引入了vtpr和vtable这两个概念.对于每个有虚函数的类,C++都会为其生成一个vtable,并在类中添加一个隐含的数据成员vptr. 对于vptr在对象中的位置,跟类的数据成员的布局一样,C++标准里面并没有做出任何的规定.但是对于特定的编译器,我们还是可以通过研究C++对象的内存布局来确定vtpr到底是放在哪里.
      下面我们通过分析C++对象的内存布局,来寻找vptr的位置.在开始讨论之前我们先提供一个模板函数void PrintLayout(T const & obj),该函数用于打印obj所在内存的内容,下面是该函数的实现:


PrintLayout.hxx#pragma once
#include <iostream>
#include <iomanip>
#include <ReinterpretCast.hxx>template<typename T>
void PrintLayout(T const & obj)
{int * pObj = ReinterpretCast<int*>(&obj);for (int i =0; i<sizeof(obj)/sizeof(int);++i){std::cout<<std::setw(10)<< pObj[i]<<std::endl;}
}


接下来通过代码来分析一下在C++里,在没有继承,单继承,多继承以及虚继承等情况下对象的内存布局,下面是示例代码,为了减少代码量,我们将类的所有数据成员设为public的,在这里我们用struct来代替class:



//main.cpp#include <iostream>
#include <PrintLayout.hxx>
#include <typeinfo>
using namespace std;struct NoVirtualMemFunc
{int  Func1(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int  Func2(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};
struct Base1
{virtual int  Base1Func1(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}virtual int  Base1Func2(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};
struct Base2
{virtual int  Base2Func1(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}virtual int  Base2Func2(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};struct D1:public Base1
{virtual int  D1Func(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};
struct D:public Base1,public Base2
{virtual int  DFunc(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};struct VD1:public virtual Base1
{virtual int  VD1Func(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};
struct VD2:public virtual Base1
{virtual int  D2Func(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};
struct VD:public VD1,public VD2
{int m_iData;
};template<typename T>
void PRINT_LAYOUT(T const & obj)
{cout<<"The layout of "<<typeid(obj).name()<<"----------------"<<endl;PrintLayout(obj);cout<<endl;
}
int main(int argc, char* argv[])
{//没有继承,没有虚函数的情况{NoVirtualMemFunc obj;obj.m_iData = 100;PRINT_LAYOUT(obj);}//没有继承,有虚函数的情况{Base1 obj;obj.m_iData = 100;PRINT_LAYOUT(obj);}//单继承{D1 obj;obj.Base1::m_iData = 100;obj.m_iData = 200;PRINT_LAYOUT(obj);}//多继承{D obj;obj.Base1::m_iData  = 100;obj.Base2::m_iData  = 200;obj.m_iData = 300;PRINT_LAYOUT(obj);}//虚拟继承{VD1 obj;obj.Base1::m_iData = 100;obj.m_iData = 200;PRINT_LAYOUT(obj);}//棱形继承{VD obj;obj.Base1::m_iData = 100;obj.VD1::m_iData   = 200;obj.VD2::m_iData   = 300;obj.m_iData       = 500;PRINT_LAYOUT(obj);}return 0;
}//输出
/*
The layout of struct NoVirtualMemFunc----------------100The layout of struct Base1----------------4294656100The layout of struct D1----------------4294740100200The layout of struct D----------------42948001004294776200300The layout of struct VD1----------------429487642948882004294864100The layout of struct VD----------------42949444294968200429493242949523005004294920100请按任意键继续. . .



对于有虚表的函数,从上面的输出我们可以得到以下结论,

1.没有继承情况,vptr存放在对象的开始位置,以下是Base1的内存布局

vptr : 4294656

m_iData :100


 2.单继承的情况下,对象只有一个vptr,它存放在对象的开始位置,派生类子对象在父类子对象的最后面,以下是D1的内存布局

vptr : 4294740

B1:: m_iData : 100

B2:: m_iData :200


3.多继承情况下,对象会为每个有虚函数的父类子对象提供一个vptr,派生类子对象在所有父类子对象的最后面,所有父类子对象按照声明顺序排列,以下是D的内存布局

B1::vptr : 4294800

B1::m_iData :100

B2::vptr : 4294776

B2::m_iData :200

D::m_iData :300


4. 虚拟继承情况下,虚父类子对象会放在派生类子对象之后,派生类子对象的第一个位置存放着一个vptr,虚拟子类子对象也会保存一个vptr,以下是VD1的内存布局

VD1::vptr :4294876

 Unknown : 4294888

VD1::m_iData : 200

B1::vptr :4294864

B1::m_iData :100


5. 棱形继承的情况下,非虚基类子对象在派生类子对象前面,并按照声明顺序排列,虚基类子对象在派生类子对象后面

VD1::vptr :        4294944

VD1::Unknown : 4294968

VD1::m_iData :  200

VD2::vptr :    4   294932

VD2::Unknown : 4294952

VD2::m_iData : 300

VD::m_iData : 500

B1::vptr :       4294920

B1::m_iData :  100


接下来我们将通过代码来验证前面结论的准确性.下面的代码具有一定的局限性.在调试以下代码的时候,对虚拟继承遇到了以下几个让我迷惑的问题:

1.对于虚拟继承,函数指针的大小为12

2.用vtable里面的指针调用,this不能正确传进去

3.如果派生类的虚拟函数多于1个,则会crash

 

//main.cpp#include <iostream>
#include <GetVptr.hxx>
#include <typeinfo>
using namespace std;struct NoVirtualMemFunc
{int  Func1(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int  Func2(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};
struct Base1
{virtual int  Base1Func1(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}virtual int  Base1Func2(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};
struct Base2
{virtual int  Base2Func1(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}virtual int  Base2Func2(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};struct D1:public Base1
{virtual int  D1Func(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};
struct D:public Base1,public Base2
{virtual int  DFunc(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};struct VD1:public virtual Base1
{virtual int  VD1Func(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};
struct VD2:public virtual Base1
{virtual int  D2Func(int a,int b){cout<<__FUNCTION__<<"/tm_iData="<<m_iData<<"/ta="<<a<<"/tb="<<b<<endl;return 0;}int m_iData;
};
struct VD:public VD1,public VD2
{int m_iData;
};template<class T>
struct MemFuncT
{typedef int (T::* T_MemFuncT)(int,int);typedef int (T::* T_MemDataT);
};
template<class C>
void CallMemFunc(int iFuncNum,int (C::**vptr)(int,int),C& obj,int a =500,int b =600)
{for (int i =0;i<iFuncNum;++i){//cout<<ReinterpretCast<void*>(vptr[i])<<"   ";(obj.*vptr[i])(a,b);}cout<<endl;
}
int main(int argc, char* argv[])
{//没有继承,有虚函数的情况{cout<<"//没有继承,有虚函数的情况"<<endl;Base1 obj;obj.m_iData = 100;MemFuncT<Base1>::T_MemFuncT * vptr = ReinterpretCast<MemFuncT<Base1>::T_MemFuncT *>(GetVptr(obj));CallMemFunc(2,vptr,obj);}//单继承{cout<<"//单继承"<<endl;D1 obj;obj.Base1::m_iData = 100;obj.m_iData = 200;MemFuncT<D1>::T_MemFuncT * vptr = ReinterpretCast<MemFuncT<D1>::T_MemFuncT *>(GetVptr(obj));CallMemFunc(3,vptr,obj);}//多继承{cout<<"//多继承"<<endl;D obj;obj.Base1::m_iData  = 100;obj.Base2::m_iData  = 200;obj.m_iData = 300;Base1 &objB1 = obj;MemFuncT<Base1>::T_MemFuncT * vptr = ReinterpretCast<MemFuncT<Base1>::T_MemFuncT *>(GetVptr(obj));CallMemFunc(3,vptr,objB1);Base2 &objB2 = obj;MemFuncT<Base2>::T_MemFuncT * vptrB2 = ReinterpretCast<MemFuncT<Base2>::T_MemFuncT *>(GetVptr(objB2));CallMemFunc(2,vptrB2,objB2);}#if 1//虚拟继承{cout<<"//虚拟继承"<<endl;VD1 obj;obj.Base1::m_iData = 100;obj.m_iData = 200;MemFuncT<VD1>::T_MemFuncT * vptr = ReinterpretCast<MemFuncT<VD1>::T_MemFuncT *>(GetVptr(obj));CallMemFunc(1,vptr,obj);Base1 & objB1 =obj ;MemFuncT<Base1>::T_MemFuncT * vptrB1 = ReinterpretCast<MemFuncT<Base1>::T_MemFuncT *>(GetVptr(objB1));CallMemFunc(2,vptrB1,objB1);}//棱形继承{cout<<"//棱形继承"<<endl;VD obj;obj.Base1::m_iData = 100;obj.VD1::m_iData   = 200;obj.VD2::m_iData   = 300;obj.m_iData       = 500;Base1 & objB1 = obj;MemFuncT<Base1>::T_MemFuncT * vptrB1 = ReinterpretCast<MemFuncT<Base1>::T_MemFuncT *>(GetVptr(objB1));CallMemFunc(2,vptrB1,objB1);VD1   & objVD1 =obj;MemFuncT<VD1>::T_MemFuncT * vptrVD1 = ReinterpretCast<MemFuncT<VD1>::T_MemFuncT *>(GetVptr(objVD1));CallMemFunc(1,vptrVD1,objVD1);VD2   & objVD2 =obj;MemFuncT<VD2>::T_MemFuncT * vptrVD2 = ReinterpretCast<MemFuncT<VD2>::T_MemFuncT *>(GetVptr(objVD2));//CallMemFunc(1,vptrVD2,objVD2);}
#endifreturn 0;
}//输出
/*
//没有继承,有虚函数的情况
Base1::Base1Func1       m_iData=100     a=500   b=600
Base1::Base1Func2       m_iData=100     a=500   b=600//单继承
Base1::Base1Func1       m_iData=100     a=500   b=600
Base1::Base1Func2       m_iData=100     a=500   b=600
D1::D1Func      m_iData=200     a=500   b=600//多继承
Base1::Base1Func1       m_iData=100     a=500   b=600
Base1::Base1Func2       m_iData=100     a=500   b=600
D::DFunc        m_iData=300     a=500   b=600Base2::Base2Func1       m_iData=200     a=500   b=600
Base2::Base2Func2       m_iData=200     a=500   b=600//虚拟继承
VD1::VD1Func    m_iData=4294960 a=500   b=600Base1::Base1Func1       m_iData=100     a=500   b=600
Base1::Base1Func2       m_iData=100     a=500   b=600//棱形继承
Base1::Base1Func1       m_iData=100     a=500   b=600
Base1::Base1Func2       m_iData=100     a=500   b=600VD1::VD1Func    m_iData=4295032 a=500   b=600请按任意键继续. . .
*/





本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/254783.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Visual Studio Code 常用插件整理

常用插件说明&#xff1a; 一、HTML Snippets 超级使用且初级的H5代码片段以及提示 二、HTML CSS Support 让HTML标签上写class智能提示当前项目所支持的样式 三、Debugger for Chrome 让vscode映射chrome的debug功能&#xff0c;静态页面都可以用vscode来打断点调试、配饰稍…

sublime 正则搜索日语字符

sublime 正则搜索日语字符 [\x{3041}-\x{3096}\x{30A0}-\x{30FF}\x{3400}-\x{4DB5}\x{4E00}-\x{9FCB}\x{F900}-\x{FA6A}\x{2E80}-\x{2FD5}\x{FF5F}-\x{FF9F}\x{3000}-\x{303F}\x{31F0}-\x{31FF}\x{3220}-\x{3243}\x{3280}-\x{337F}\x{FF01}-\x{FF5E}] 参考: http://www.localiz…

函数域中申请堆空间出函数后不会自动释放

#include<iostream> using std::cout; using std::endl; char *scat(char *s1,char *s2) { char *snew char[strlen(s1)strlen(s2)1]; strcpy(s,s1); strcat(s,s2); return s;//返回刚申请的堆空间的首址&#xff0c;出了函数后变量s就不复存在了&#xff0c;但申请的…

/home文件夹重新划分独立分区

1. 备份home文件夹&#xff0c;数据丢失的时候可以使用cp -av /home/* /data/2. 磁盘分区2.1 虚拟机中添加磁盘并让系统识别&#xff0c;使用# echo - - - >> /sys/class/scsi_host/host0/scan# echo - - - >> /sys/class/scsi_host/host1/scan# echo - - - >…

使用halcon将一个圆上的点拟合成圆形并且求出圆心

我们在自动化贴装机标定过程中&#xff0c;需要计算吸头的旋转中心位置。我们一般使用的方法是使用模板匹配&#xff0c;做一个模板&#xff0c;吸头旋转一个角度寻找模板一次&#xff0c;通过多次旋转求取吸头的旋转中心。 使用halcon实现 public bool FitCircle(double[] X…

This 指针

如果你期望衍生类别重新定义一个成员函数&#xff0c;那么你应该在基础类别中把此函数设为 virtual。 以单一指令唤起不同函数&#xff0c;这种性质称为Polymorphism&#xff0c;意思是"the ability toassume many forms"&#xff0c;也就是多态。 虚拟函…

史上最全亚历山大大帝名言

&#xff08;1&#xff09;把财富分给他人&#xff0c;把希望留给自己&#xff0c;他将带给我无穷的财富。——亚历山大大帝 &#xff08;2&#xff09;山不走到我这里来&#xff0c;我就走到他那里去。——亚历山大大帝 &#xff08;3&#xff09;能够战胜恐惧就能战胜死亡。—…

jdbcmysql

做java开发难免会用到数据库,操作数据库也是java开发的核心技术。那我们现在就来谈谈javajdbc来操作mysql数据库吧 第一步&#xff1a;我们需要把mysql的驱动引进来这里引驱动就是把mysql-connector-java-5.1.37-bin.jar加到项目中来&#xff0c;下面附jar包 第二步&#xff1a…

char **p, char a[16][8]; 问:p=a 是否会导致程序在以后出现问题?为什么?

int (*v)[10]; 在 Visual C 里面不能与 int **v 等同起来。 举个例子&#xff1a; int **p; int (*v)[10]; int a[10][10]; 如果写 v a 是可以的。 如果写 p a 将会引起一个类型不匹配的编译错误。 二级指针&#xff08;int **p&#xff09;需要自己指向一个一级指针&#x…

draw_circle_mod预生成交互式圆形

目录draw_circle_mod&#xff08;算子&#xff09;描述参数draw_circle_mod&#xff08;算子&#xff09; draw_circle_mod - 圆的交互式绘图。 draw_circle_mod&#xff08;:: WindowHandle&#xff0c;RowIn&#xff0c;ColumnIn&#xff0c;RadiusIn&#xff1a;Row&#…

川崎机器人c#通讯(转)

由于本人在工业自动化行业做机器视觉的工作&#xff0c;所以除了图像处理方面要掌握外&#xff0c;还需要与工业机器人进行通信。最近学习了计算机与川崎机器人的TCP/IP通信&#xff0c;于是在这里记录一下。 除了直接与机器人通信外&#xff0c;有一种方式是通过PLC间接通信&a…

模板类 Template Classes 以及模板类编译时的处理

&#xfeff;&#xfeff;我们可以建立template classes&#xff0c;使它们能够神奇地操作任何类型的资料。下面这个例子是让CThree 类别储存三个成员变量&#xff0c;成员函数Min 传回其中的最小值&#xff0c;成员函数Max 则传回其中的最大值。我们把它设计为template class&…

行转列及列转行查询

开发过程中常遇到行转列或是列转行的问题&#xff0c;即需要将数据库中一张表信息进行行转列操作&#xff0c;再将每列&#xff08;即每个字段&#xff09;作为与其他表进行联表查询的字段进行显示。 一、行转列&#xff1a;将原来同一列下多行的不同内容作为多个字段&#xff…

移动端系列讲解之字体单位

移动端字体单位有哪些&#xff1f;他们的兼容性如何&#xff1f;他们的特点&#xff1f;1.移动端字体单位现在主要有 em 、rem 、px 2.兼容性请传送点击这里 em: em是相对长度单位。相对于父元素设置的字体大小。em相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸…

x264_param_t参数注解

typedef struct x264_param_t { CPU 标志位 unsigned int cpu; int i_threads; 并行编码多帧 int b_deterministic; 是否允许非确定性时线程优化 int i_sync_lookahead; 线程超前缓冲 视频属性 int i_width; 宽度 int i_height; 高…

gen_circle_contour_xld创建圆或圆弧的XLD轮廓

目录gen_circle_contour_xld&#xff08;算子&#xff09;描述参数gen_circle_contour_xld&#xff08;算子&#xff09; gen_circle_contour_xld - 创建圆或圆弧的XLD轮廓。 gen_circle_contour_xld&#xff08;&#xff1a;ContCircle&#xff1a;Row&#xff0c;Column&am…

一、Java语言基础(4)_方法和数组——数组

2018-04-25 不悔梦归处&#xff0c;只恨未尽心 数组 一、一维数组 数组的含义&#xff1a;具有相同类型的多个变量按有序形式组织起来的数据形式。&#xff08;数组是用来存储固定大小的同类型元素。&#xff09;数组的定义&#xff1a;方式1&#xff08;推荐使用&#xff09;&…

http header 具体解释

HTTP&#xff08;HyperTextTransferProtocol&#xff09;即超文本传输协议&#xff0c;眼下网页传输的的通用协议。HTTP协议採用了请求/响应模型&#xff0c;浏览器或其它client发出请求&#xff0c;server给与响应。就整个网络资源传输而言&#xff0c;包含message-header和me…

研究生开题报告需要注意的几点

&#xfeff;&#xfeff;1 毕业论文选题的原则 毕业论文选题一般要求满足以下原则&#xff1a; ①开拓性:前人没有专门研究过或虽已研究但尚无理想的结果&#xff0c;有待进一步的探讨和研究&#xff0c;或是学术界有分歧&#xff0c;有必要深入研究探讨的问题&#xff1b;…

create_metrology_model创建测量几何形状所需的数据结构(原理)

目录create_metrology_model&#xff08;算子&#xff09;描述二维计量的基本原理创建计量模型数据结构提供近似值修改模型参数修改对象参数对齐计量模型应用测量访问结果清理记忆注意参数create_metrology_model&#xff08;算子&#xff09; create_metrology_model - 创建测…