致谢


  本文是基于对<Inside the c++ object model>的阅读和gdb的使用而完成的.在此感谢Lippman对cfront中对象模型的解析,这些解析帮助读者拨开迷雾.此外,Linux下无比强大的gdb工具更是驱散"黑暗"的"明灯".  :)

No-Inheritance


 class Base {
public:
int a = ;
static int b;
int c = ; void showBase1();
static int showBase2();
}; void Base::showBase1() {
cout<<"Base"<<endl;
}
int Base::showBase2() {
cout<<"base2"<<endl;
}

解析:

  使用GDB查看内存空间,显示

    Base中a的地址为0x7fffffffdc50

    Base中c的地址为0x7fffffffdc54

    Base中b的地址为0x601068

    showBase1的地址为0x40085e(参数为Base * const -> this指针为常量指针)

    showBase2的地址为0x400888(参数为void)

  显然

    non-static data member是存储在class object中;

    static data member, member function因为是所有本class的对象所share的,所以放置在了一个公共区域;

Inheritance without Polymorphism


 class Base {
public:
int a = ;
static int b;
int c = ; void showBase1();
};
int Base::b = ;
void Base::showBase1() {
cout<<"Base"<<endl;
} class Inheri : public Base {
public:
int c = ;
static int d; static int showInheri1();
};
int Inheri::d = ;
int Inheri::showInheri1(){
cout<<"Inheri"<<endl;
}

解析:  

  使用GDB查看内存空间,显示

    Inheri中a的地址为0x7fffffffdc70

    Inheri中c的地址为0x7fffffffdc78

    Inheri中b的地址为0x601068

    Inheri中b的地址为0x60106c

    showBase1的地址为0x40085e(参数为Base * const -> this指针为常量指针)

    showInheri1的地址为0x400888(参数为void)

  显然

    derived class object中包含 基类和派生类的non-static data member;

No-Inheritance with Polymorphism


 class Base {
public:
int a = ;
static int b;
int c = ; virtual void showBase1();//virtual function
};
int Base::b = ;
void Base::showBase1() {
cout<<"Base"<<endl;
}

  使用gdb查看base class object( p ptr )

     " {_vptr.Base = 0x400af0 <vtable for Base+16>, a = 21, static b = 10, c = 22} "

  在类中使用虚机制(虚函数,虚基类,虚继承..)时,会为每个object添加vptr来指向所对应的vtbl.

  

  使用gdb查看vptr指向的虚函数(p /a *(void**)0x400af0

    " {0x40092e <Base::showBase1()>, 0x697265686e4936} " 

  

Single-Inheritance with Virtual Mechanism


class Base {
public:
int a = ;
static int b;
int c = ; virtual void showBase1();
};
int Base::b = ;
void Base::showBase1() {
cout<<"Base"<<endl;
} class Inheri : public Base {
public:
int c = ;
static int d; static int showInheri1();
};
int Inheri::d = ;
int Inheri::showInheri1(){
cout<<"Inheri"<<endl;
}

  查看Inheri class object

    {<Base> = {_vptr.Base = 0x400bc0 <vtable for Inheri+16>, a = 21, static b = 10, c = 22}, c = 23, static d = 11}

  查看Ineri class object 的vtbl

    0x400998 <Inheri::show()>

  可以看到derived class object直接使用了从base class subobject中继承而来的vptr.

  同样,查看Base class object

    {_vptr.Base = 0x400be0 <vtable for Base+16>, a = 21, static b = 10, c = 22}

  查看Base class object的vtbl

    0x40096e <Base::show()>         

  

  从这里可以看出来,在Single Inheritance中每个class object的vtbl中都只包含本class所对应的virtual function.

  

  我们再测试一下derived class赋值给base class pointer的情况.

    Base* bbptr = new Inheri;

  查看bbptr所指向的内存:

    {<Base> = {_vptr.Base = 0x400bc0 <vtable for Inheri+16>, a = 21, static b = 10, c = 22}, c = 23, static d = 11}

  查看vtbl中的内容:

    0x400998 <Inheri::show()>

  这里我们可以有两点发现:

    1) 虽然使用的是基类指针Base* 来接Inheri对象,但是其vptr所指向的vtbl仍然是Inheri class的;

    2) 每个class所对应的vtbl在内存中只有一份,在测试中bbptr和iptr指向的vtbl都是位于0x400bc0

Multiple-Inheritance with Virtual Mechanism


    

 class Base {
public:
int a = ;
static int b;
int c = ; virtual void show();//inline
};
int Base::b = ;
void Base::show() {
cout<<"Base"<<endl;
} class Inheri : public Base {
public:
int c = ;
static int d; virtual void show();
};
int Inheri::d = ;
void Inheri::show(){
cout<<"Inheri"<<endl;
} class OtherBase{
public:
int oa;
virtual void show();
};
void OtherBase::show(){
cout<<"OtherBase"<<endl;
} class Final : public OtherBase,public Inheri{
public:
virtual void show();
};
void Final::show() {
cout<<"Final"<<endl;
}

  查看Final对象

    "{<OtherBase> = {_vptr.OtherBase = 0x400cd0 <vtable for Final+16>, oa = 0}, <Inheri> = {<Base> = {_vptr.Base = 0x400ce8 <vtable for Final+40>, a = 21, static b = 10, c = 22}, c = 23, static d = 11}, <No data fields>}"

    可以观察到:

      1) 子对象从右向左的被构建

      2) 分别包含OtherBase和Base的vptr,这是为了在derived class object 赋予base class object时更容易处理.

继续查看OtherBase和Base中vptr的信息

    _vptr.OtherBase所指向的vtbl中信息为 : 0x400a5c <Final::show()>

     _vptr.Base所指项的vtbl中信息为 : 0x400a86 <_ZThn16_N5Final4showEv>

    可以看到这两个vtbl所保存的都是Final::show.因此, 通过Final来为各个基类指针赋值时,最后总是调用Final自身的虚函数.

Virtual Inheritance


  

 class _ios {
public:
int i;
virtual void show();
};
void _ios::show() {
cout<<"ios"<<endl;
} class _istream : public _ios {
public:
int is;
virtual void show();
};
void _istream::show() {
cout<<"istream"<<endl;
} class _ostream : public _ios {
public:
int os;
virtual void show();
};
void _ostream::show() {
cout<<"ostream"<<endl;
} class _iostream : public _istream, public _ostream {
public:
int ios;
virtual void show();
};
void _iostream::show() {
cout<<"iostream"<<endl;
}

  在未使用virtual inheritance时, 查看 _iostream 对象, 会看到两份 _ios 类的对象,分别属于_istream和_ostream :

    "{<_istream> = {<_ios> = {_vptr._ios = 0x400c90 <vtable for _iostream+16>, i = 0}, is = 0}, <_ostream> = {<_ios> = {

_vptr._ios = 0x400ca8 <vtable for _iostream+40>, i = 0}, os = 0}, ios = 0}"

    符合之前介绍的single inheritance with polymorphism, _istream和_ostream分别使用从_ios中而来的vptr.ios来指向自己的vtbl. 

  使用virtual inheritance时 ,可以看到只有一份 _ios对象:

    "{<_istream> = {<_ios> = {_vptr._ios = 0x400d18 <vtable for _iostream+88>, i = 0}, _vptr._istream = 0x400cd8 <vtable for _iostream+24>, is = 0}, <_ostream> = {_vptr._ostream = 0x400cf8 <vtable for _iostream+56>, os = 0}, ios = 0}"

    在虚继承中,没有和单一继承中那样继承基类的vptr, 而是拥有自己的vptr.

继续查看vtbl中内容,分别显示 :

    0x400a40 <_ZTv0_n24_N9_iostream4showEv>

    0x400a3a <_ZThn16_N9_iostream4showEv>

    可见, 虚函数表中的函数也都是_iostream class中的member function.因此, 无论_iostream对象赋值给那个base class subobject的指针,总能调用到_iostream class的virtual function.

Reference


  <Inside the C++ Object Model>

  <GDB Manul>

备注


  更多内容详见 https://github.com/CarlSama/Inside-The-CPP-Object-Model-Reading-Notes

C++ 虚函数机制学习的更多相关文章

  1. C++中对C的扩展学习新增内容———面向对象(继承)函数扩展性及虚函数机制

    1.c语言中的多态,动态绑定和静态绑定 void do_speak(void(*speak)()) { speak(); } void pig_speak() { cout << &quo ...

  2. 浅谈C++虚函数机制

    0.前言 在后端面试中语言特性的掌握直接决定面试成败,C++语言一直在增加很多新特性来提高使用者的便利性,但是每种特性都有复杂的背后实现,充分理解实现原理和设计原因,才能更好地掌握这种新特性. 只要出 ...

  3. 匹夫细说C#:从园友留言到动手实现C#虚函数机制

    前言 上一篇文章匹夫通过CIL代码简析了一下C#函数调用的话题.虽然点击进来的童鞋并不如匹夫预料的那么多,但也还是有一些挺有质量的来自园友的回复.这不,就有一个园友提出了这样一个代码,这段代码如果被编 ...

  4. [C/C++] 虚函数机制

    转自:c++ 虚函数的实现机制:笔记 1.c++实现多态的方法 其实很多人都知道,虚函数在c++中的实现机制就是用虚表和虚指针,但是具体是怎样的呢?从more effecive c++其中一篇文章里面 ...

  5. 【高级】C++中虚函数机制的实现原理

    多态是C++中的一个重要特性,而虚函数却是实现多态的基石.所谓多态,就是基类的引用或者指针可以根据其实际指向的子类类型而表现出不同的功能.这篇文章讨论这种功能的实现原理,注意这里并不以某个具体的编译器 ...

  6. C++ 多态、虚函数机制以及虚函数表

    1.非virtual函数,调用规则取决于对象的显式类型.例如 A* a  = new B(); a->display(); 调用的就是A类中定义的display().和对象本体是B无关系. 2. ...

  7. c++虚函数的学习

    1.虚函数 #include<iostream.h> class Base { public: void print() { cout<<"Base"< ...

  8. [置顶] 【C/C++学习】之十三、虚函数剖析

    所谓虚函数,虚就虚在“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的.由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被称为“ ...

  9. 你好,C++(37)上车的人请买票!6.3.3 用虚函数实现多态

    6.3.3  用虚函数实现多态 在理解了面向对象的继承机制之后,我们知道了在大多数情况下派生类是基类的“一种”,就像“学生”是“人”类中的一种一样.既然“学生”是“人”的一种,那么在使用“人”这个概念 ...

随机推荐

  1. 20个热门jQuery的提示和技巧

    以下是一些非常有用的jQuery提示和所有jQuery的开发技巧. 1.优化性能复杂的选择 查询DOM中的一个子集,使用复杂的选择时,大幅提高了性能: var subset = $("&qu ...

  2. Cassandra - Non-system keyspaces don't have the same replication settings, effective ownership information is meaningless

    In cassandra 2.1.4, if you run "nodetool status" without any keyspace specified, you will ...

  3. Asp.Net Web API 2

    Asp.Net Web API 2第十八课——Working with Entity Relations in OData   前言 阅读本文之前,您也可以到Asp.Net Web API 2 系列导 ...

  4. oracle 日期to_char转换24小时制12小时制

    1>以12小时制显示 SQL>select to_char(sysdate,'YYYY-MM-DD HH12:MI:SS AM')from dual; TO_CHAR(SYSDATE,'Y ...

  5. 黑科技——编写一个无法卸载的App

    之前经常听到朋友或者新闻媒体上报道说,有的朋友的android手机中病毒了,出现了软件无法卸载的情况,对于我这样一个从事android开发程序员来说,我还不是太相信(毕竟自己还是有点菜,哈哈).今天在 ...

  6. 不用css样式表和背景图片实现圆角矩形,超简洁!

    当网站页面的整体布局设计好后,接下来有很多细节的实现是很让人头疼的.其中之一就是圆角矩形的实现. 在网上看了很多圆角矩形的实现方法,基本有两种,一种是用纯css实现,不需要背景图片:另一种是用背景图像 ...

  7. 10个简单步骤,完全理解SQL

    此文章为转载 1. SQL 是一种声明式语言 首先要把这个概念记在脑中:“声明”. SQL 语言是为计算机声明了一个你想从原始数据中获得什么样的结果的一个范例,而不是告诉计算机如何能够得到结果.这是不 ...

  8. Toolkit Pro学习--Toolbar的创建

    一.新建Toolkit Pro应用程序 安装好Toolkit Pro后,打开VS2008,新建-项目-Toolkit Pro Application.如图1所示. 图1:Toolkit Pro创建窗口 ...

  9. linux 下访问mysql

    1:先进到root:/# /usr/local/mysql/bin/2:root:/# mysql -u root -p     Enter password:

  10. Node.js包管理器:

    Node.js包管理器: 当我们要把某个包作为工程运行的一部分时,通过本地模式获取,如果要在命令行下使用,则使用全局模式安装 使用全局模式安装的包并不能直接在JavaScript文件中用require ...