当前位置: 首页 > 编程笔记 >

浅谈C++ 类的实例中 内存分配详解

竺鸿骞
2023-03-14
本文向大家介绍浅谈C++ 类的实例中 内存分配详解,包括了浅谈C++ 类的实例中 内存分配详解的使用技巧和注意事项,需要的朋友参考一下

一个类,有成员变量:静态与非静态之分;而成员函数有三种:静态的、非静态的、虚的。

那么这些个东西在内存中到底是如何分配的呢?

以一个例子来说明:

#include"iostream.h"
class CObject
{
public:
  static int a;
  CObject();
  ~CObject();
  void Fun();
private: 
int m_count; 
int m_index;
};
VoidCObject::Fun(){  cout<<"Fun\n"<<endl;}
CObject::CObject(){  cout<<"Construct!\n";}
CObject::~CObject(){  cout<<"Destruct!\n";}
int CObject::a=1;
void main(){
cout<<"Sizeof(CObject):"<<sizeof(CObject)<<endl; cout<<"CObject::a="<<CObject::a<<endl;
CObject myObject;cout<<"sizeof(myObject):"<<sizeof(myObject)<<endl;
cout<<"sizeof(int)"<<sizeof(int)<<endl;
}

 这是我的一段测试代码, 运行结果是:

 

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是针对实例,而普通成员函数,是针对类体的,一个类的成 员函数,多个实例也共用相同的函数指针,所以自然不能归为实例的大小,这在我的另一篇博文有提到。

接着看下面一段代码

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; 
} 
 class Base {
 public:
Base();
virtual ~Base(); //每个实例都有虚函数表
void set_num(int num) { a=num; } //普通成员函数,为各实例公有,不归入sizeof统计
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;

class Derive:public Base
 
{
 
public:
 
  Derive():Base(){};
 
  ~Derive(){};
 
private:
 
  static int st;
 
  int d;
 
  char *p;
 
  char c;
 
};
 
 class Derive:public Base {
 
public:
 
 Derive():Base(){};
 
 ~Derive(){};
 
private:
 
static int st;
 
 int d;
 
char *p;
 
 char c;
 
};

这个时候,结果就变成了

12

24

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

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

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

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

以上就是小编为大家带来的浅谈C++ 类的实例中 内存分配详解全部内容了,希望大家多多支持小牛知识库~

 类似资料:
  • 本文向大家介绍浅谈C++中派生类对象的内存布局,包括了浅谈C++中派生类对象的内存布局的使用技巧和注意事项,需要的朋友参考一下 主要从三个方面来讲:   1 单一继承   2 多重继承   3 虚拟继承 1 单一继承 (1)派生类完全拥有基类的内存布局,并保证其完整性。 派生类可以看作是完整的基类的Object再加上派生类自己的Object。如果基类中没有虚成员函数,那么派生类与具有相同功能的非派

  • 本文向大家介绍C++中的内存对齐实例详解,包括了C++中的内存对齐实例详解的使用技巧和注意事项,需要的朋友参考一下 C++中的内存对齐实例详解 内存对齐          在我们的程序中,数据结构还有变量等等都需要占有内存,在很多系统中,它都要求内存分配的时候要对齐,这样做的好处就是可以提高访问内存的速度。 我们还是先来看一段简单的程序:            程序一       这段程序的功能很

  • 本文向大家介绍浅谈C# 类的继承,包括了浅谈C# 类的继承的使用技巧和注意事项,需要的朋友参考一下 继承 一个类可以继承自另一个类。在 C#中,类与类之间只存在单一继承。也就是说,一个类的直接基类只能有一个。当类与类之间实现继承的时候,子类可以将它的直接基类的所有成员当做自己的成员,除了类的静态构造方法、实例构造方法和析构方法。但是,虽然基类的所有成员都可以当做子类的成员,但是如果基类的成员设置了

  • 本文向大家介绍浅谈C#中ListView类的用法,包括了浅谈C#中ListView类的用法的使用技巧和注意事项,需要的朋友参考一下 一、ListView类   1、常用的基本属性: (1)FullRowSelect:设置是否行选择模式。(默认为false) 提示:只有在Details视图该属性才有意义。 (2)GridLines:设置行和列之间是否显示网格线。(默认为false)提示:只有在Det

  • 本文向大家介绍浅谈java+内存分配及变量存储位置的区别,包括了浅谈java+内存分配及变量存储位置的区别的使用技巧和注意事项,需要的朋友参考一下 Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识。一般Java在内存分配时会涉及到以下区域: ◆寄存器:我

  • 本文向大家介绍浅谈c++中的stl中的map用法详解,包括了浅谈c++中的stl中的map用法详解的使用技巧和注意事项,需要的朋友参考一下 Map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。这里说下map内部数据的组织,map