类中内容在内存中到底是如何分配的呢?

2024-06-11 23:08

本文主要是介绍类中内容在内存中到底是如何分配的呢?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


分类:

      一个类,有成员变量:静态与非静态之分;而成员函数有三种:静态的、非静态的、虚的。
      那么这些个东西在内存中到底是如何分配的呢?
      以一个例子来说明:

      [html]view plaincopyprint?

  1.       #include"iostream.h"  
  2.       class CObject  
  3.       {  
  4.       public:  
  5.       static int a;  
  6.       CObject();  
  7.       ~CObject();  
  8.       void Fun();  
  9.       
  10.       private:  
  11.       int m_count;  
  12.       int m_index;  
  13.       };  
  14.       void CObject::Fun()  
  15.       {  
  16.       cout<<"Fun\n"<<endl;  
  17.       }  
  18.       CObject::CObject()  
  19.       {  
  20.       cout<<"Construct!\n";  
  21.       }  
  22.       CObject::~CObject()  
  23.       {  
  24.       cout<<"Destruct!\n";  
  25.       }  
  26.       int CObject::a=1;  
  27.       void main()  
  28.       {  
  29.       cout<<"Sizeof(CObject):"<<sizeof(CObject)<<endl;  
  30.       //CObject::Fun();  
  31.       cout<<"CObject::a="<<CObject::a<<endl;  
  32.       CObject myObject;  
  33.       cout<<"sizeof(myObject):"<<sizeof(myObject)<<endl;  
  34.       cout<<"sizeof(int)"<<sizeof(int)<<endl;  
  35.       
  36.       }  
这是我的一段测试代码, 
运行结果是: 
Sizeof(CObject):8 
CObject::a=1 
Construct! 
sizeof(myObject):8 
sizeof(int)4 
Destruct! 
我有疑问如下: 
(1) C++中,应该是对象才会被分配内存空间吧??为什么CObject内存大小是8,刚好和两个 成员变量的大小之和一致!难道还没实例化的时候,类就 已经有了内存空间了? 
(2)当对象生成了之后,算出的内存大小怎么还是8,函数难道不占用内存空间吗?至少应该放个 函数指针在里面的吧?内存是怎样布局的?
(3) 静态成员应该是属于类的,怎么类的大小中没有包含静态成员的大小?
下面分别解答如下:
1)Sizeof(CObject)是在编译时就计算了的,一个类定义了,它所占的内存编译器就已经知道了,这时只是得到它占用的大小,并没有分配内存操作 。也可以这样想:编译器肯定知道大小了,这与分配内存空间无关,知道大小了,以后实例化了才能知道要分配多大。
2)类的普通成员、静态成员函数是不占类内存的,至于你说的函数指针在你的类中有 虚函数的时候存在一个 虚函数表指针,也就是说如果你的类里有虚函数则 sizeof(CObject)的值会增加4个字节。
其实 类的成员函数 实际上与 普通的 全局函数一样。 
只不过编译器在编译的时候,会在成员函数上加一个参数,传入这个对象的指针。
成员函数地址是全局已知的,对象的内存空间里根本无须保存成员函数地址。 
对成员函数(非虚函数)的调用在编译时就确定了。 
像 myObject.Fun() 这样的调用会被编译成形如 _CObject_Fun( &myObject ) 的样子。
函数是不算到sizeof中的,因为函数是代码,被各个对象共用,跟数据处理方式不同。对象中不必有函数指针,因为对象没必要知道它的各个函数的地址(调 用函数的是其他代码而不是该对象)。 
类的属性是指类的数据成员,他们是实例化一个对象时就为数据成员分配内存了,而且每个对象的数据成员是对立的,而成员函数是共有的~ 
静态成员函数与一般成员函数的唯一区别就是没有this指针,因此不能访问非静态数据成员。总之,程序中的所有函数都是位于代码区的。
3)静态成员并不属于某个对象,sizeof取的是对象大小。
知道了上面的时候,就可以改一下来看看:
我也补充一些: 
class CObject 

public: 
static int a; 
CObject(); 
~CObject(); 
void Fun(); 
private: 
double m_count;  //这里改成了double 
int  m_index; 
}; 
这个类用sizeof()测出来的大小是 2*sizeof(double)=16 
class CObject 

public: 
static int a; 
CObject(); 
~CObject(); 
void Fun(); 
private: 
char m_count;  //这里改成了char 
int  m_index; 
}; 
大小是2*sizeof(int)=8 
class CObject 

public: 
static int a; 
CObject(); 
~CObject(); 
void Fun(); 
private: 
double m_count;  //这里改成了double 
int  m_index; 
char  c; 
}; 
sizeof(char)+sizeof(int) <sizeof(double) 所以大小是2*sizeof(double) 
其实这里还有一个是内存对齐的问题。
空类大小是1。 
另外要注意的一些问题:

      先看一个空的类占多少空间?

      class Base
      {
      public:
      Base();
      ~Base();
      }; 

class Base { public: Base(); ~Base(); };

      注意到我这里显示声明了构造跟析构,但是sizeof(Base)的结果是1.

      因为一个空类也要实例化,所谓类的实例化就是在内存中分配一块地址,每个实例在内存中都有独一无二的地址。同样空类也会被实例化,所以编译器会给空类隐含 的添加一个字节,这样空类实例化之后就有了独一无二的地址了。所以空类的sizeof为1。

      而析构函数,跟构造函数这些成员函数,是跟sizeof无关的,也不难理解因为我们的sizeof是针对实例,而普通成员函数,是针对类体的,一个类的成 员函数,多个实例也共用相同的函数指针,所以自然不能归为实例的大小,这在我的另一篇博文有提到。

      接着看下面一段代码

      [html]view plaincopyprint?

  1.       class Base   
  2.       {   
  3.       public:   
  4.       Base();                   
  5.       virtual ~Base();         //每个实例都有虚函数表   
  6.       void set_num(int num)    // 普通成员函数,为各实例公有,不归入sizeof统计   
  7.       {   
  8.       a=num;   
  9.       }   
  10.       private:   
  11.       int  a;                  //占4字节   
  12.       char *p;                 //4字节指针   
  13.       };   
  14.       
  15.       class Derive:public Base   
  16.       {   
  17.       public:   
  18.       Derive():Base(){};         
  19.       ~Derive(){};   
  20.       private:   
  21.       static int st;         //非实例独占   
  22.       int  d;                     //占4字节   
  23.       char *p;                    //4字节指针   
  24.       
  25.       };   
  26.       
  27.       int main()     
  28.       {     
  29.       cout<<sizeof(Base)<<endl;   
  30.       cout<<sizeof(Derive)<<endl;   
  31.       return 0;   
  32.       }   
class Base { public: Base(); virtual ~Base(); //每个实例都有虚函数表 void set_num(int num) //普通成员函数,为各实例公有,不归入sizeof统计 { a=num; } private: int a; //占4字节 char *p; //4字节指针 }; class Derive:public Base { public: Derive():Base(){}; ~Derive(){}; private: static int st; //非实例独占 int d; //占4字节 char *p; //4字节指针 }; int main() { cout<<sizeof(Base)<<endl; cout<<sizeof(Derive)<< endl; return 0; }

      结果自然是

      12

      20

      Base类里的int  a;char *p;占8个字节。

      而虚析构函数virtual ~Base();的指针占4子字节。

      其他成员函数不归入sizeof统计。

      Derive类首先要具有Base类的部分,也就是占12字节。

      int  d;char *p;占8字节

      static int st;不归入sizeof统计

      所以一共是20字节。

      在考虑在Derive里加一个成员char c;

  1.       class Derive:public Base 
  2.       { 
  3.       public: 
  4.       Derive():Base(){}; 
  5.       ~Derive(){}; 
  6.       private: 
  7.       static int st; 
  8.       int  d; 
  9.       char *p; 
  10.       char c; 
  11.       
  12.       }; 
class Derive:public Base { public: Derive():Base(){}; ~Derive(){}; private: static int st; int d; char *p; char c; };

      这个时候,结果就变成了

      12

      24

      一个char c;增加了4字节,说明类的大小也遵守类似class字节对齐,的补齐规则。

      具体的可以看我那篇《5分钟搞定字节对齐》

      至此,我们可以归纳以下几个原则:

      1.类的大小为类的非静态成员数据的类型大小之和,也 就是说静态成员数据不作考虑。

      2.普通成员函数与sizeof无关。

      3.虚函数由于要维护在虚函数表,所以要占据一个指针大小,也就是4字节。

      4.类的总大小也遵守类似class字节对齐的,调整规则。

      转载自:http://www.blue1000.com/bkhtml/c151/2010-11/69613.htm

这篇关于类中内容在内存中到底是如何分配的呢?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1052506

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

Python实现自动化Word文档样式复制与内容生成

《Python实现自动化Word文档样式复制与内容生成》在办公自动化领域,高效处理Word文档的样式和内容复制是一个常见需求,本文将展示如何利用Python的python-docx库实现... 目录一、为什么需要自动化 Word 文档处理二、核心功能实现:样式与表格的深度复制1. 表格复制(含样式与内容)2

Java内存区域与内存溢出异常的详细探讨

《Java内存区域与内存溢出异常的详细探讨》:本文主要介绍Java内存区域与内存溢出异常的相关资料,分析异常原因并提供解决策略,如参数调整、代码优化等,帮助开发者排查内存问题,需要的朋友可以参考下... 目录一、引言二、Java 运行时数据区域(一)程序计数器(二)Java 虚拟机栈(三)本地方法栈(四)J

Java如何将文件内容转换为MD5哈希值

《Java如何将文件内容转换为MD5哈希值》:本文主要介绍Java如何将文件内容转换为MD5哈希值的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java文件内容转换为MD5哈希值一个完整的Java示例代码代码解释注意事项总结Java文件内容转换为MD5

使用Python自动化生成PPT并结合LLM生成内容的代码解析

《使用Python自动化生成PPT并结合LLM生成内容的代码解析》PowerPoint是常用的文档工具,但手动设计和排版耗时耗力,本文将展示如何通过Python自动化提取PPT样式并生成新PPT,同时... 目录核心代码解析1. 提取 PPT 样式到 jsON关键步骤:代码片段:2. 应用 JSON 样式到

java变量内存中存储的使用方式

《java变量内存中存储的使用方式》:本文主要介绍java变量内存中存储的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、变量的定义3、 变量的类型4、 变量的作用域5、 内存中的存储方式总结1、介绍在 Java 中,变量是用于存储程序中数据