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

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

相关文章

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

Python内存优化的实战技巧分享

《Python内存优化的实战技巧分享》Python作为一门解释型语言,虽然在开发效率上有着显著优势,但在执行效率方面往往被诟病,然而,通过合理的内存优化策略,我们可以让Python程序的运行速度提升3... 目录前言python内存管理机制引用计数机制垃圾回收机制内存泄漏的常见原因1. 循环引用2. 全局变

Java使用正则提取字符串中的内容的详细步骤

《Java使用正则提取字符串中的内容的详细步骤》:本文主要介绍Java中使用正则表达式提取字符串内容的方法,通过Pattern和Matcher类实现,涵盖编译正则、查找匹配、分组捕获、数字与邮箱提... 目录1. 基础流程2. 关键方法说明3. 常见场景示例场景1:提取所有数字场景2:提取邮箱地址4. 高级

C#高效实现Word文档内容查找与替换的6种方法

《C#高效实现Word文档内容查找与替换的6种方法》在日常文档处理工作中,尤其是面对大型Word文档时,手动查找、替换文本往往既耗时又容易出错,本文整理了C#查找与替换Word内容的6种方法,大家可以... 目录环境准备方法一:查找文本并替换为新文本方法二:使用正则表达式查找并替换文本方法三:将文本替换为图

Linux从文件中提取特定内容的实用技巧分享

《Linux从文件中提取特定内容的实用技巧分享》在日常数据处理和配置文件管理中,我们经常需要从大型文件中提取特定内容,本文介绍的提取特定行技术正是这些高级操作的基础,以提取含有1的简单需求为例,我们可... 目录引言1、方法一:使用 grep 命令1.1 grep 命令基础1.2 命令详解1.3 高级用法2

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

最新Spring Security的基于内存用户认证方式

《最新SpringSecurity的基于内存用户认证方式》本文讲解SpringSecurity内存认证配置,适用于开发、测试等场景,通过代码创建用户及权限管理,支持密码加密,虽简单但不持久化,生产环... 目录1. 前言2. 因何选择内存认证?3. 基础配置实战❶ 创建Spring Security配置文件

java内存泄漏排查过程及解决

《java内存泄漏排查过程及解决》公司某服务内存持续增长,疑似内存泄漏,未触发OOM,排查方法包括检查JVM配置、分析GC执行状态、导出堆内存快照并用IDEAProfiler工具定位大对象及代码... 目录内存泄漏内存问题排查1.查看JVM内存配置2.分析gc是否正常执行3.导出 dump 各种工具分析4.

linux批量替换文件内容的实现方式

《linux批量替换文件内容的实现方式》本文总结了Linux中批量替换文件内容的几种方法,包括使用sed替换文件夹内所有文件、单个文件内容及逐行字符串,强调使用反引号和绝对路径,并分享个人经验供参考... 目录一、linux批量替换文件内容 二、替换文件内所有匹配的字符串 三、替换每一行中全部str1为st