zoukankan      html  css  js  c++  java
  • C++对象模型浅析

    本文仅代表博主自己对C++内存对象模型的一点理解,如果文中有

    理解偏差和不准确的地方,希望各位大大提出,我好及时改正。

    本博文只对博主自己负责,不对任何人负责。

     

    就如《深度探索C++对象模型》一书中介绍的C++的封装并没有给C++带来过多的开销。然而面向对象的编程方法却给广大的编程者提供了一种

    更为开阔的编程思路。

     

    好,我们主要看看前面一句。开销是什么,这里的开销主要指C++类所占内存的空间。首先,我们看这样一个例子,我们定义一个结构体和类,结

    构体和类中含有相同的数据成员。除此之外再无其他。我们看看这个结构体和类的大小是多少:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 struct A
     5 {
     6     int a;
     7     int b;
     8     int c;
     9 };
    10 
    11 class B
    12 {
    13     int a;
    14     int b;
    15     int c;
    16 };
    17 
    18 
    19 
    20 int main()
    21 {
    22     cout<<"sizeof(A)="<<sizeof(A)<<endl;
    23     cout<<"sizeof(B)="<<sizeof(B)<<endl;
    24     return 0;
    25 }

    结果:

    看来只要数据成员相同,那么就算声明为类,也不会带来额外的内存开销。

    现在我们再看一个例子,我们让类B更加复杂一下,我们添加一个静态成员变量和,一个静态成员函数,一个非静态成员函数,一个

    虚函数:我们再看看这个例子的结果将是什么样的结果:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 struct A
     5 {
     6     int a;
     7     int b;
     8     int c;
     9 };
    10 
    11 class B
    12 {
    13 private:
    14     int a;
    15     int b;
    16     int c;
    17     static int d;
    18 
    19 public:
    20     static void fun1()
    21     {
    22         cout<<"This is a static fun1."<<endl;
    23     }
    24     
    25     void fun2()
    26     {
    27         cout<<"This is ordinary fun2."<<endl;
    28     }
    29 
    30     virtual void fun3()
    31     {
    32         cout<<"This is a virtual fun3."<<endl;
    33     }
    34 };
    35 
    36 
    37 
    38 int main()
    39 {
    40     cout<<"sizeof(A)="<<sizeof(A)<<endl;
    41     cout<<"sizeof(B)="<<sizeof(B)<<endl;
    42     return 0;
    43 }

    结果:

    在这里我们增加了一个静态数据成员d,一个静态成员函数fun1,非静态的成员函数fun2一个虚函数fun3.不难发现就类的代码规模

    来说,的确增加了不少,然而结果仅仅比之前增加了四个字节的开销。《深度探索C++对象模型》中有这样一句话说C++封装所带来

    的开销主要来源于虚函数。

    那么我们可以先看看结构体A的内存分布:可以产生结构体A的一个对象:

    1     A *a;
    2     a=new A;

    对象a的内存结构如下:

    显然a中有三个整型数据元素a,b,c刚好12个字节

     

    现在我们看看类B的内存分布

    可以产生类B的一个对象:

    1     B *b;
    2     b=new B;

    对象B的内存结构如下:

    可以看到尽管类B封装的很多的内容,但是较之A而言仅仅多了一个__vfptr,我们不禁回忆起前面的一句话,C++的封装的开销主要来自于虚

    函数。其实如果对C++有一定了解的同学都不难猜出,这个ptr其实就是一个虚表指针。正因为这个虚表指针给封装带来了额外的4个字节的开销。

    那么虚表指针是什么呢,虚表指针其实就是指向虚函数表的一个指针。当一个类中有虚函数的时候,类会自动生成一个指针,该指针保存的是该类

    中第一个虚函数的地址。所以就算有多个虚函数仍然只需要保存一个虚表指针,然后通过这个指针逐个遍历就可以取得各个虚函数的地址。然而关于

    虚函数在C++中功能特性我们留待下次一起学习了。

    这下一目了然了吧。

     

    那么我们可以总结一下影响类的内存开销主要有以下几个方面:

    1.非静态的数据成员

    2.虚函数表指针

    3.当然既然类也是要考虑内存对齐的。

     

    还有几点需要注意:

    1.类的静态数据成员存储在全局变量区,不带来C++内存开销,该静态数据成员属于整个类的不属于具体某个对象,其初始化要在类外进行。

    2.类的静态成员函数是属于整个类的,不属于某个对象,不会带来内存开销。注意类的静态成员函数中不能调用非静态的成员变量。

    3.类的普通成员函数始终在程序的代码区中保存一份,不带来内存开销。

     

     

    最后再通过一张图片只管展示结构体A和类B的内存布局:

  • 相关阅读:
    JDBC之Statement 接口的测试(存在sql注入风险)
    Java操作数据库之JDBC增删改查
    Java实体类之间的映射(一对多关系)
    Java实体类之间的映射(一对一关系)
    yield(放弃、谦逊、礼让)
    daemon(守护、服务员)-t1.setDaemon(true)
    join当前线程等待指定的线程结束后才能继续运行
    检查你要加入到gradle的第三方library是否是最新版本
    git 无法忽略Android Studio 生成的 .idea目录解决办法
    mybatis3 step by step 快速上手
  • 原文地址:https://www.cnblogs.com/vpoet/p/4682318.html
Copyright © 2011-2022 走看看