浅谈C++对象模型

更多文章分享在个人微信公众号:极客熊猫
欢迎扫码关注:
在这里插入图片描述

引言

如《Effective C++》中所言,C++是一个语言联邦,它由以下四部分组成:

  • C:可以理解为兼容C的那部分,即面向过程的;
  • Object-Oriendted C++:即C++面向对象的部分,封装、继承、多态;
  • Template C++:即泛型编程;
  • STL:标准模版库,主要包含容器、迭代器、算法等。

面向过程:如C语言,数据和**处理数据的操作(即函数)**是分开的,也就是说语言本身并没有支持数据和函数之间的关联性。

本文我们要谈的就是C++面向对象的部分内容。

  • 封装:于C++而言,封装实际上指的就是class,通过class把数据和函数封装在一起,对外只提供类的接口,而把实现细节隐藏起来,同时还可以通过访问权限制定数据和函数的安全等级,从而提高了安全性和隐私性。
  • 继承:继承可以理解为代码复用,是为了提高代码的复用性和可扩展性。子类继承父类,在保留“家族传统”的同时,还允许子类有自己的“小个性”。对于父类中的数据成员,子类完整的继承下来,所谓“完整”是指这种继承是要占用内存的;而对于父类的成员函数,子类继承的只是函数的调用权
  • 多态:多态可以理解为接口复用,也就是通过不同的方式调用“相同的接口”将产生不同的操作。多态分为静态多态和动态多态,静态多态通过重载实现,动态多态通过虚函数实现。

多态中,关于“相同的接口”中的“相同”,不同形式的多态有一些程度上的区分。一个函数由返回类型、函数名、函数形参、函数体四部分组成。

  • 静态多态:通过重载实现,“相同”指的是函数名相同,函数形参必须不同,返回类型相同不相同都可以,既然要实现不同功能,函数体当然也是不同的;
  • 动态多态:通过子类重写父类的虚函数实现,“相同”指的是除了函数体外其他完全相同,返回类型、函数名、函数形参都相同,只有函数体不同(为实现不同功能)。

静态多态是在编译期就确定下来的,编译器编译的时候会把这些函数加上各自的形参信息,这样实际上还是不同的函数,从而实现静态多态。重载的函数都在同一个类里面。

动态多态是在运行期才能确定下来。

C++对象基本模型

所谓C++对象模型,可以理解为对于各种支持的底层实现机制,这里我们简单关注C++对象在内存中的布局。
C++类的成员可以归纳为以下两大类五小类:

  • 成员函数
    • 静态成员函数(static member functions)
    • 非静态成员函数(non-static member functions)
    • 虚成员函数(virtual member functions)
  • 数据成员
    • 静态数据成员(static data members)
    • 非静态数据成员(non-static data members)

那么,我们可以定义这样一个类Base,在不考虑继承的情况下,它囊括了类的所有可能的成员。

class Base{
public:
    static int fun_1(); //static member function
    int fun_2(); //non-static member function
    virtual int fun_3(){} //virtual member function
private:
    static int data_1; //static data member
    int data_2; //non-static data member
};

对于这样一个类Base,实例化后,它的对象占多少字节呢?先给出答案:

  • 在32位机器上,sizeof(Base)得到的值为8;
  • 在64位机器上,sizeof(Base)得到的值为16。

为什么是这样的值呢?这是由C++对象模型所决定的。在C++对象模型中:

  • 非静态数据成员(如data_2)由类的每个对象各自保存,根据对象的内存分配方式,存储在Heap或Stack;
  • 静态数据成员(如data_1)只分配一次内存,由类的所有对象共用,存储在数据段(.data);
  • 静态成员函数(如fun_1)和非静态成员函数(如fun_2)均存储在代码段(.text);
  • 虚函数(如fun_3)也存储在代码段(.text),并以以下2个步骤支持之:
    • 类产生一堆指向虚成员函数的指针,这些指针放在一个表中,称为虚表(virtual table);
    • 类的每个对象都存储一个指针vptr,它指向虚表。

vptr指针存放在对象内存的前四个字节,虚表存放在只读数据段(.rodata)。

也就是说,类实例化后,对象内只有非静态数据成员和虚指针vptr。这就能解释为什么sizeof(Base)的结果是8(32位机器)和16(32位机器)了:

  • int型的data_1占4个字节;
  • vptr是指针,与机器相关,32位下占4字节,64位下占8字节;
  • 内存对齐填补的空间。

以上三部分加起来,就是每个Base类所占的内存大小。

C++对象内存布局

对于上述Base类,以32位平台为例,用以下分配方式分配内存后,其内存布局如下图所示。

int main(){
	Base* ptr = new Base;
}

在这里插入图片描述

注:上图的内存布局旨在描述一种通用模型,而具体的要视平台、编译器而定。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客熊猫GeekPanda

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值