栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

c++内存分配之堆和栈

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

c++内存分配之堆和栈

堆和栈

栈Stack,一般调用函数时,在作用域中会栈用来存放参数和返回地址。

在消亡时自动归还内存

堆Heap,系统提供的全局内存空间,一般通过动态分配取得的都是这种全局内存。

结束后要手动释放内存,否则内存泄漏


  • 栈对象

在作用域中创建的对象,可以理解成临时变量。

在作用域结束时自动消亡,执行析构。因此也叫自动(auto)对象

  • 静态对象&全局对象

相比普通的栈对象,生存期扩大到了整个程序。程序结束后才会消亡

某些情况会导致内存泄漏

全局对象因为在main外面,又只能放在前面,所以可以说是全局

  • 堆对象

为指针开辟内存空间,要在指针改变或消亡前释放空间,才能结束。

若没有释放空间,则对象一直存在,内存泄漏。

内存泄漏: 本来划的一块内存因为某些原因不再被控制了。

指针可以改变指向,而且有的指针存放在栈中,当指针被释放后,会失去对内存的控制。


在使用new的时候,实际先调用operator new分配内存,再调用构造函数创建对象。

//以Complex对象为例,当执行Complex* pc = new Complex(1, 2);时
Complex *pc;
void* mem = operator new( sizeof(Complex) ); //operator new不是运算符,是函数,实质是调用malloc。但operator new可重载
pc = static_cast(mem); //c++特有的类型转换方式,当然用c的方式也行
pc->Complex::Complex(1,2); //通过指向对象的指针调用构造函数。(此时参数this等于pc

在delete时,先析构,再回收内存。

//以String为例,我们动态分配一个String对象,其内部又是一个动态分配。当delete ps时
String::~String(ps);  //内容是delete[]字符串,所以共delete两次
operator delete(ps);  //实质是调用free,同operator new

  • 拓展:动态内存分配,真正所得的内存块(以VC为例,了解底层)

单个对象:

  1. 如果new了一个Complex,其中包含 2*4=8 字节。而为了标记会在前后各加4字节的cookie。cookie记录了这块new的内存的主要信息并起引导作用(可以认为指针通过cookie进而指向对象本身)

VC的特点是内存为16字节的倍数。我们以4字节为一个单元(方便看,毕竟 int 就是4字节),则为4单元的倍数。

  • 正常模式:此时这块内存为 8 + 2 * 4 = 16,分配完成,并将cookie记录为11h(16进制的16+1,16为占用内存,末位1或0标记此处是否占用。因此占用内存必须是16的倍数,即4单元的倍数)
  • 调试模式:Complex前面会有8个单元的调试头,后面会紧跟着加1个调试用单元,共9个。所以要加3个pad单元,cookie记为41h

pad 的意思就是垫子。咲夜的那个

  1. 如果是String,其内容只有一个指针,4个字节
  • 正常模式:需要加一个pad,分配完成
  • 调试模式:2+9+1=12是4的倍数。不用加pad

而String内又会指向另外的内存分配,也要经历这样的过程,但分配的是字符串

new 函数将内存分配进行了封装,相比底层的 malloc ,更加直观

对象数组:

  1. Complex* p = new Complex [3];cookie和调试字节不变,只是要加一个int(4字节)用来记录数组容量3,内容变成3块Complex连起来,即6个单元(也要构造3次)

    再实际计算需要的pad数和cookie数据。垫,都可以垫

  2. String* p = new String[3];同上

以上解释了数组为何是物理意义上的连续内存

回收内存:原理是读cookie存储的数据,直接整体回收对应大小的内存。单个数据用delete,只析构一次;而数组特别是含指针的如String类要用delete[],将数组中每个对象分别析构。

  • 简单的类不需要特别的析构函数,可以当做打包的基本数据,所以简单数组也可以delete
  • 而String等类析构时要回收指针指向的具体内存,否则内存泄漏
  • delete 的原理是搜索内存首尾的 cookie 并执行一次析构
  • 而delete[ ]会逐个检查每个单元,对每个元素分别执行析构

  • 小补充:

要在栈(作用域)中创建对象时,编译器会检查析构函数的访问性。如果为private或protected则无法创建(很合理)。

这样可保证只能在堆中建立对象,防止在栈中建立。若有子类,则析构需要重载,作为虚函数,放在protected更合适。

相对应的,要防止在堆中建立对象,可以将标准的new函数放入private中。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/996359.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号