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

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

相关文章

C#比较两个List集合内容是否相同的几种方法

《C#比较两个List集合内容是否相同的几种方法》本文详细介绍了在C#中比较两个List集合内容是否相同的方法,包括非自定义类和自定义类的元素比较,对于非自定义类,可以使用SequenceEqual、... 目录 一、非自定义类的元素比较1. 使用 SequenceEqual 方法(顺序和内容都相等)2.

Linux内存泄露的原因排查和解决方案(内存管理方法)

《Linux内存泄露的原因排查和解决方案(内存管理方法)》文章主要介绍了运维团队在Linux处理LB服务内存暴涨、内存报警问题的过程,从发现问题、排查原因到制定解决方案,并从中学习了Linux内存管理... 目录一、问题二、排查过程三、解决方案四、内存管理方法1)linux内存寻址2)Linux分页机制3)

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

大数据小内存排序问题如何巧妙解决

《大数据小内存排序问题如何巧妙解决》文章介绍了大数据小内存排序的三种方法:数据库排序、分治法和位图法,数据库排序简单但速度慢,对设备要求高;分治法高效但实现复杂;位图法可读性差,但存储空间受限... 目录三种方法:方法概要数据库排序(http://www.chinasem.cn对数据库设备要求较高)分治法(常

Redis多种内存淘汰策略及配置技巧分享

《Redis多种内存淘汰策略及配置技巧分享》本文介绍了Redis内存满时的淘汰机制,包括内存淘汰机制的概念,Redis提供的8种淘汰策略(如noeviction、volatile-lru等)及其适用场... 目录前言一、什么是 Redis 的内存淘汰机制?二、Redis 内存淘汰策略1. pythonnoe

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

关于Java内存访问重排序的研究

《关于Java内存访问重排序的研究》文章主要介绍了重排序现象及其在多线程编程中的影响,包括内存可见性问题和Java内存模型中对重排序的规则... 目录什么是重排序重排序图解重排序实验as-if-serial语义内存访问重排序与内存可见性内存访问重排序与Java内存模型重排序示意表内存屏障内存屏障示意表Int

如何测试计算机的内存是否存在问题? 判断电脑内存故障的多种方法

《如何测试计算机的内存是否存在问题?判断电脑内存故障的多种方法》内存是电脑中非常重要的组件之一,如果内存出现故障,可能会导致电脑出现各种问题,如蓝屏、死机、程序崩溃等,如何判断内存是否出现故障呢?下... 如果你的电脑是崩溃、冻结还是不稳定,那么它的内存可能有问题。要进行检查,你可以使用Windows 11

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚: