浅谈C++对象的内存分布和虚函数表

c++中一个类中无非有四种成员:静态数据成员和非静态数据成员,静态函数和非静态函数。

1、非静态数据成员被放在每一个对象体内作为对象专有的数据成员。

2、静态数据成员被提取出来放在程序的静态数据区内,为该类所有对象共享,因此只存在一份。

3、静态和非静态成员函数最终都被提取出来放在程序的代码段中并为该类所有对象共享,因此每一个成员函数也只能存在一份代码实体。在c++中类的成员函数都是保存在静态存储区中的 ,那静态函数也是保存在静态存储区中的,他们都是在类中保存同一个惫份。

因此,构成对象本身的只有数据,任何成员函数都不隶属于任何一个对象,非静态成员函数与对象的关系就是绑定,绑定的中介就是this指针。成员函数为该类所有对象共享,不仅是处于简化语言实现、节省存储的目的,而且是为了使同类对象有一致的行为。同类对象的行为虽然一致,但是操作不同的数据成员。

#include"iostream.h"
class A
{

private:
 int x;
 int y;

public:
 void sety()
 {
 y=5;
 }
 void print()
 {
 cout<<"x="<<x<<endl<<"y="<<y<<endl;
 }
};

void main()
{
 A a;
 a.sety();
 a.print();
 int *p=(int *)&a;//对象a的内存模型里面,只有非静态数据成员,所以是合理的。
 *p=6;
 a.print();
}

4、单继承的对象的内存布局,第一个为虚函数表指针vtbl,其后为成员且先基类后子类,虚函数表里包含了所有的虚函数的地址,以NULL结束。虚函数如果子类有重写,就由子类的重新的代替。

<一> 上述类图的解释:B的v2覆盖了A的v2,C的v1覆盖了A的v1,C的v3覆盖了B的v3.

注意:发生覆盖的特征有:

1) 不同的范围(分别位于派生类与基类);

2) 函数名字相同;

3) 参数相同;

4) 基类函数必须有virtual关键字。

成员函数被重载的特征
 
(1)相同的范围(在同一个类中);
 
(2)函数名字相同;
 
(3)参数不同;
 
(4)virtual 关键字可有可无。

 
“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下
 
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
 
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

<二> 类C的对象的内存模型为:

单继承的对象的布局,第一个为虚函数表指针vtbl,其后为成员且先基类后子类,虚函数表里包含了所有的虚函数的地址,以NULL结束。虚函数如果子类有重写,就由子类的重新的代替。

5、与单继承相同的是所有的虚函数都包含在虚函数表中,所不同的多重继承有多个虚函数表,当子类对父类的虚函数有重写时,子类的函数替换父类的函数在对应的虚函数位置,当子类有新的虚函数时,这些虚函数被加在第一个虚函数表的后面。

<一> 对类图的解释:D的v3覆盖了B3的v3,另外D有一个新的虚函数vD。

<二> class D 的内存模型

总结:与单继承相同的是所有的虚函数都包含在虚函数表中,所不同的多重继承有多个虚函数表,当子类对父类的虚函数有重写时,子类的函数覆盖父类的函数在对应的虚函数位置,当子类有新的虚函数时,这些虚函数被加在第一个虚函数表的后面

6. 虚继承,使公共的基类在子类中只有一份,我们看到虚继承在多重继承的基础上多了vbtable来存储到公共基类的偏移。

<一>类图的解释:D2覆盖了B的vB,GD覆盖了D1的vD1同时也覆盖B的vB,GD也有自己的虚函数vGD。

<二> class GD的内存模型

总结:虚继承,使公共的基类在子类中只有一份,我们看到虚继承在多重继承的基础上多了vbtable来存储到公共基类的偏移

以上就是小编为大家带来的浅谈C++对象的内存分布和虚函数表全部内容了,希望大家多多支持我们~

(0)

相关推荐

  • C++ 虚函数的详解及简单实例

    C++ 虚函数的详解 虚函数的使用和纯虚函数的使用. 虚函数是在基类定义,然后子类重写这个函数后,基类的指针指向子类的对象,可以调用这个函数,这个函数同时保留这子类重写的功能. 纯虚函数是可以不用在基类定义,只需要声明就可以了,然后因为是纯虚函数,是不能产生基类的对象,但是可以产生基类的指针. 纯虚函数和虚函数最主要的区别在于,纯虚函数所在的基类是不能产生对象的,而虚函数的基类是可以产生对象的. // pointers to base class #include <iostream> usi

  • c++中虚函数的实现详解

    前言 c++ 分为编译时多态和运行时多态.运行时多态依赖于虚函数,大部分人或许听说过虚函数是由虚函数表+虚函数指针实现的,但,真的是这样吗?虽然 c++ 规范有着复杂的语言细节,但底层实现机制却任由编译器厂商想象.(没准某种特殊的处理器电路结构原生支持虚函数,没准这个处理器压根不是冯纽曼型,或者将来厂商发明了比虚函数表更有效率的数据结构.) 虚函数表 封装把实例的数据和操作结合在了一起,但实例本身只有数据,没有函数,同一个类的函数是共享的.我们通过一个例子来间接证明这一点 class Base1

  • C++中的多态与虚函数的内部实现方法

    1.什么是多态 多态性可以简单概括为"一个接口,多种行为". 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可以用自己的方式去响应共同的消息.所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数.这是一种泛型技术,即用相同的代码实现不同的动作.这体现了面向对象编程的优越性. 多态分为两种: (1)编译时多态:主要通过函数的重载和模板来实现. (2)运行时多态:主要通过虚函数来实现. 2.几个相关概念 (1)覆盖.

  • 浅谈C++中虚函数实现原理揭秘

    编译器到底做了什么实现的虚函数的晚绑定呢?我们来探个究竟. 编译器对每个包含虚函数的类创建一个表(称为V TA B L E).在V TA B L E中,编译器放置特定类的虚函数地址.在每个带有虚函数的类 中,编译器秘密地置一指针,称为v p o i n t e r(缩写为V P T R),指向这个对象的V TA B L E.通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个V P T R,并在V TA B L E表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生

  • 探讨C++中不能声明为虚函数的有哪些函数

    常见的不不能声明为虚函数的有:普通函数(非成员函数):静态成员函数:内联成员函数:构造函数:友元函数. 1.为什么C++不支持普通函数为虚函数? 普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数. 多态的运行期行为体现在虚函数上,虚函数通过继承方式来体现出多态作用,顶层 函数不属于成员函数,是不能被继承的 2.为什么C++不支持构造函数为虚函数? 这个原因很简单,主要是从语义上考虑,所以不支持.因为构造函数本来就是为了

  • C/C++杂记 虚函数的实现的基本原理(图文)

    1. 概述 简单地说,每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针.例: 其中: B的虚函数表中存放着B::foo和B::bar两个函数指针. D的虚函数表中存放的既有继承自B的虚函数B::foo,又有重写(override)了基类虚函数B::bar的D::bar,还有新增的虚函数D::quz. 提示:为了描述方便,本文在探讨对象内存布局时,将忽略内存对齐对布局的影响. 2. 虚函数表构造过程 从编译器的角度来说,

  • 浅谈C++基类的析构函数为虚函数

    1.原因: 在实现多态时, 当用基类指针操作派生类, 在析构时候防止只析构基类而不析构派生类. 2.例子: (1). #include<iostream> using namespace std; class Base{ public: Base() {}; ~Base() {cout << "Output from the destructor of class Base!" << endl;}; void DoSomething() { cout

  • C++对象内存分布详解(包括字节对齐和虚函数表)

    1.C++对象的内存分布和虚函数表: C++对象的内存分布和虚函数表注意,对象中保存的是虚函数表指针,而不是虚函数表,虚函数表在编译阶段就已经生成,同类的不同对象中的虚函数指针指向同一个虚函数表,不同类对象的虚函数指针指向不同虚函数表. 2.何时进行动态绑定: (1)每个类对象在被构造时不用去关心是否有其他类从自己派生,也不需要关心自己是否从其他类派生,只要按照一个统一的流程:在自身的构造函数执行之前把自己所属类(即当前构造函数所属的类)的虚函数表的地址绑定到当前对象上(一般是保存在对象内存空间

  • C++之普通成员函数、虚函数以及纯虚函数的区别与用法要点

    普通成员函数是静态编译的,没有运行时多态,只会根据指针或引用的"字面值"类对象,调用自己的普通函数:虚函数为了重载和多态的需要,在基类中定义的,即便定义为空:纯虚函数是在基类中声明的虚函数,它可以再基类中有定义,且派生类必须定义自己的实现方法. 假设我们有三个类Person.Teacher.Student它们之间的关系如下: 类的关系图 普通成员函数 [Demo1] 根据这个类图,我们有下面的代码实现 #ifndef __OBJEDT_H__ #define __OBJEDT_H__

  • 浅谈C++对象的内存分布和虚函数表

    c++中一个类中无非有四种成员:静态数据成员和非静态数据成员,静态函数和非静态函数. 1.非静态数据成员被放在每一个对象体内作为对象专有的数据成员. 2.静态数据成员被提取出来放在程序的静态数据区内,为该类所有对象共享,因此只存在一份. 3.静态和非静态成员函数最终都被提取出来放在程序的代码段中并为该类所有对象共享,因此每一个成员函数也只能存在一份代码实体.在c++中类的成员函数都是保存在静态存储区中的 ,那静态函数也是保存在静态存储区中的,他们都是在类中保存同一个惫份. 因此,构成对象本身的只

  • C++虚函数表与类的内存分布深入分析理解

    目录 不可定义为虚函数的函数 将析构函数定义为虚函数的作用 虚函数表原理 继承关系中虚函数表结构 多重继承的虚函数表 多态调用原理 对齐和补齐规则 为什么要有对齐和补齐 资源链接 不可定义为虚函数的函数 类的静态函数和构造函数不可以定义为虚函数: 静态函数的目的是通过类名+函数名访问类的static变量,或者通过对象调用staic函数实现对static成员变量的读写,要求内存中只有一份数据.而虚函数在子类中重写,并且通过多态机制实现动态调用,在内存中需要保存不同的重写版本. 构造函数的作用是构造

  • C++ 类中有虚函数(虚函数表)时 内存分布详解

    虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的.简称为V-Table.在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承.覆盖的问题,保证其容真实反应实际的函数.这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数. 这里我们着重看一下这张虚函数表.C++的编译器应该是

  • 浅谈Python 对象内存占用

    一切皆是对象 在 Python 一切皆是对象,包括所有类型的常量与变量,整型,布尔型,甚至函数. 参见stackoverflow上的一个问题 Is everything an object in python like ruby 代码中即可以验证: # everythin in python is object def fuction(): return print isinstance(True, object) print isinstance(0, object) print isinst

  • 浅谈python对象数据的读写权限

    面向对象的编程语言在写大型程序的的时候,往往比面向过程的语言用起来更方便,安全.其中原因之一在于:类机制. 类,对众多的数据进行分类,封装,让一个数据对象成为一个完整的个体,贴近现实生活,高度抽象化.但是,python对类的封装并不好,因为所有的属性和方法都是公开的,你可以随意访问或者写入,你可以在类的外部对类的属性进行修改,甚至添加属性.这的确让人感到不安. 下面就来总结一下学习后的解决方案. 1,使用2个下划线前缀隐藏属性或者方法. __xxx #!/usr/bin/python3 #-*-

  • 浅谈Java堆外内存之突破JVM枷锁

    对于有Java开发经验的朋友都知道,Java中不需要手动的申请和释放内存,JVM会自动进行垃圾回收:而使用的内存是由JVM控制的. 那么,什么时机会进行垃圾回收,如何避免过度频繁的垃圾回收?如果JVM给的内存不够用,怎么办? 此时,堆外内存登场!利用堆外内存,不仅可以随意操控内存,还能提高网络交互的速度. 背景1:JVM内存的分配 对于JVM的内存规则,应该是老生常谈的东西了,这里我就简单的说下: 新生代:一般来说新创建的对象都分配在这里. 年老代:经过几次垃圾回收,新生代的对象就会放在年老代里

  • 浅谈java对象结构 对象头 Markword

    概述 对象实例由对象头.实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度; | 类型 | 32位JVM | 64位JVM| | ------ ---- | ------------| --------- | | markword | 32bit | 64bit | | 类型指针 | 32bit |64bit ,开启指针压缩时为32bit | | 数组长度 | 32bit |32bit | header.png compressed_header.png 可以看到

  • 浅谈redis采用不同内存分配器tcmalloc和jemalloc

    我们知道Redis并没有自己实现内存池,没有在标准的系统内存分配器上再加上自己的东西.所以系统内存分配器的性能及碎片率会对Redis造成一些性能上的影响. 在Redis的 zmalloc.c 源码中,我们可以看到如下代码: /* Double expansion needed for stringification of macro values. */ #define __xstr(s) __str(s) #define __str(s) #s #if defined(USE_TCMALLOC

  • java web学习_浅谈request对象中get和post的差异

    阅读目录(Content) •1.get与post的区别 •1.1 get方法 jsp中的代码form表单代码 •1.2 action包中servlet的doGet方法中的代码 •2.运行结果 •2.1 输入数据 •2.2 打印出数据 •3.post方法 •4.对比 •4.1 在输出页面按下F12查看 •5.分析 1.get与post的区别 Get和Post方法都是对服务器的请求方式,只是他们传输表单的方式不一样. 下面我们就以传输一个表单的数据为例,来分析get与Post的区别 1.1 get

随机推荐