浅谈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++ 类的实例中 内存分配详解全部内容了,希望大家多多支持我们~

(0)

相关推荐

  • C/C++语言中结构体的内存分配小例子

    当未用 #pragma 指令指定编译器的对齐位数时,结构体按最长宽度的数据成员的宽度对齐:当使用了 #pragma 指令指定编译器的对齐位数时,结构体按最长宽度的数据成员的宽度和 #pragma 指令指定的位数中的较小值对齐. #pragma 指令格式如下所示:#pragma pack(4)     // 或者 #pragma pack(push, 4) 举例如下:(机器字长为 32 位)    struct    {        char a;    }test;    printf("%d

  • 浅谈C++内存分配及变长数组的动态分配

    第一部分 C++内存分配 一.关于内存 1.内存分配方式 内存分配方式有三种: (1)从静态存储区域分配.内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在 例如全局变量,static变量. (2)在栈上创建.在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存 储单元自动被释放.栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限. (3) 从堆上分配,亦称动态内存分配.程序在运行的时候用malloc或new申请任意多少的内存,程序员

  • 基于C++内存分配、函数调用与返回值的深入分析

    在谈述函数调用和返回值问题之前,先来看看C++中内存分配的问题. C++编译器将计算机内存分为代码区和数据区,很显然,代码区就是存放程序代码,而数据区则是存放程序编译和执行过程出现的变量和常量.数据区又分为静态数据区.动态数据区,动态数据区包括堆区和栈区.以下是各个区的作用:(1)代码区:存放程序代码:(2)数据区a.静态数据区: 在编译器进行编译的时候就为该变量分配的内存,存放在这个区的数据在程序全部执行结束后系统自动释放,生命周期贯穿于整个程序执行过程.b.动态数据区:包括堆区和栈区堆区:这

  • C++动态内存分配(new/new[]和delete/delete[])详解

    C++动态内存分配(new/new[]和delete/delete[])详解 为了解决这个普通的编程问题,在运行时能创建和销毁对象是基本的要求.当然,C已提供了动态内存分配函数malloc( )和free( ),以及malloc( )的变种(realloc:改变分配内存的大小,calloc:指针指向内存前初始化),这些函数在运行时从堆中(也称自由内存)分配存储单元,但是运用这些库函数需要计算需要开辟内存的大小,容易出现错误. 那么通常我们在C语言中我们开辟内存的方式如下: (void*)mall

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

    一个类,有成员变量:静态与非静态之分:而成员函数有三种:静态的.非静态的.虚的. 那么这些个东西在内存中到底是如何分配的呢? 以一个例子来说明: #include"iostream.h" class CObject { public: static int a; CObject(); ~CObject(); void Fun(); private: int m_count; int m_index; }; VoidCObject::Fun(){ cout<<"Fu

  • 浅谈int8_t int64_t size_t ssize_t的相关问题(详解)

    在代码中经常看到int8_t/int16_t/int32_t/int64_t/uint8_t/size_t/ssize_t,以前对这个问题一直是稀里糊涂的,不明白它们到底是什么数据类型,现在上班了,必须把它弄明白了 uint8_t之类 那么_t的意思到底表示什么?具体的官方答案没有找到,不过我觉得有个答案比较接近.它就是一个结构的标注,可以理解为type/typedef的缩写,表示它是通过typedef定义的,而不是其它数据类型.既然它们都不是新的数据类型,只是使用typedef给类型起的别名,

  • C语言与C++中内存管理详解

    目录 内存分布 动态内存管理方式-堆区 C语言动态内存管理 C++动态内存管理 new和delete的用法 operator new与operator delete函数 new和delete的实现原理 定位new表达式 高频面试题 重点new/delete和malloc/free的区别 内存泄漏 内存分布 主要段及其分布 ​ 每个程序运行起来以后,它将拥有自己独立的虚拟地址空间.这个虚拟地址空间的大小与操作系统的位数有关系.32位硬件平台的虚拟地址空间的地址可以从0~2^32-1,即0x0000

  • C语言 动态内存分配详解

    C语言 动态内存分配详解 动态内存分配涉及到堆栈的概念:堆栈是两种数据结构.堆栈都是数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除. 栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈. 堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表. \在C语言中,全局变量分配在内存中的静态存储区,非静态的局部变量(包括形参)是分配在内存的动态存储区,该存储区被

  • C++ 动态内存分配详解(new/new[]和delete/delete[])

    一.为什么需要动态内存分配? 在C++程序中,所有内存需求都是在程序执行之前通过定义所需的变量来确定的. 但是可能存在程序的内存需求只能在运行时确定的情况. 例如,当需要的内存取决于用户输入. 在这些情况下,程序需要动态分配内存,C ++语言将运算符new和delete合成在一起. (1)特点 1.C++中通过new关键字进行动态内存申请 2.C++中的动态内存分配是基于类型进行的 3.delete关键字用于内存释放 (2)语法 ①变量申请: Type* pointer = new Type;

  • java程序运行时内存分配详解

    一. 基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Java进程对应唯一一个JVM实例,每一个JVM实例唯一对应一个堆,每一个线程有一个自己私有的栈.进程所创建的所有类的实例(也就是对象)或数组(指的是数组的本身,不是引用)都放在堆中,并由该进程所有的线程共享.Java中分配堆内存是自动初始化的,即为一个对象分配内存的时候,会初始化这个对象中变量.虽然Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在栈中分配,也就是说

  • 浅谈C++日志系统log4cxx的使用小结详解

    本文主要从log4cxx级别.layout.格式化.命名规则.Filter几个方面介绍. 一.log4cxx命名规则Logger由一个String类的名字识别,logger的名字是大小写敏感的,且名字之间具有继承的关系,子名有父名作为前缀,用点号.分隔.如:x.y是x.y.z的父亲.根logger (root logger)是所有logger的祖先, 它具有如下属性:1) 它总是存在的:2) 它不可以通过名字获得.通过调用public static Logger Logger.getRootLo

  • 浅谈Java 类中各成分加载顺序和内存中的存放位置

    一.什么时候会加载类? 使用到类中的内容时加载:有三种情况 1.创建对象:new StaticCode(); 2.使用类中的静态成员:StaticCode.num=9;  StaticCode.show(); 3.在命令行中运行:java StaticCodeDemo 二.类所有内容加载顺序和内存中的存放位置 利用语句进行分析: 1.Person p=new Person("zhangsan",20); 该句话所做的事情: 1.在栈内存中,开辟main函数的空间,建立main函数的变量

  • 浅谈java类和对象

    目录 一.面向对象的描述 二.类和对象的基本概念 三.类定义和使用 1.简单认识类 2.类的定义 3.实例化对象 4.类的三大特性 封装 继承 多态 一.面向对象的描述 面向对象是一种现在最为流行的程序设计方法,几乎现在的所有应用都以面向对象为主了,最早的面向对象的概念实际上是由IBM提出的,在70年代的Smaltalk语言之中进行了应用,后来根据面向对象的设计思路,才形成C++,而由C++产生了Java这门面向对象的编程语言. 但是在面向对象设计之前,广泛采用的是面向过程,面向过程只是针对于自

  • C#学习笔记整理_浅谈Math类的方法

    c#中Math类的方法 Math.Abs 已重载. 返回指定数字的绝对值. Math.Acos 返回余弦值为指定数字的角度. Math.Asin 返回正弦值为指定数字的角度. Math.Atan 返回正切值为指定数字的角度. Math.Atan2 返回正切值为两个指定数字的商的角度. Math.BigMul 生成两个 32 位数字的完整乘积. Math.Ceiling 已重载. 返回大于或等于指定数字的最小整数. Math.Cos 返回指定角度的余弦值. Math.Cosh 返回指定角度的双曲余

随机推荐