c++堆(heap)、栈(stack)与内存管理

本文详细介绍了C++中堆栈和内存管理的相关知识。阐述了栈、堆的概念,不同对象(栈对象、静态局部对象、全局对象、堆对象)的生命周期,以及new和delete的操作过程。还分析了VC中动态分配内存块和数组的情况,强调了避免内存泄漏,如字符串中array new要搭配array delete。

Stack

Stack是存在于某作用域(scope)的一块内存空间(memory space)。例如当你调用函数,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址。
在函数本体(function body)内声明的任何变量,其所使用的内存块都取自上述stack

Heap

Heap或谓system heap,是指由操作系统提供的一块global内存空间,程序可动态分配(dynamic allocated),从中获得若干块(blocks)

例子:

class Complex {...};
...
{	//c1所占用的空间来自stack
	Complex c1(1,2);
	//Complex(3)是个临时对象,其所占用的空间用new来获得由heap动态分配的内存空间,并由p指向这个空间。动态获得的空间有责任用完后释放其占用的内存空间
	Complex* p = new Complex(3)//创建一个复数初值为3,内存来自于heap
}

Note

  • 两个对象c1和指针p当离开当前作用域{ }的时候,c1的生命死亡(因为它在栈里面),但是如果你是通过new从堆里面取得的内存或对象等等任何东西,你都必须手动把它释放掉

stack objects的生命期(栈对象)

class Complex {...};
...
{	
	Complex c1(1,2);//c1只要离开作用域便会死亡,之后析构函数(清理数据)就会被调用
}

c1便是所谓stack object,其生命在作用域(scope)结束之际结束。
这种作用域内的object,又称为auto object,因为它被自动清理


static local objects的生命期(静态局部对象)

class Complex {...};
...
{
	static Complex c2(1,2);
}

c2便是所谓static object,其生命在作用域(scope)结束之际仍然存在直到整个程序结束。


global objects的生命期(全局对象)

class Complex {...};
	...
Complex c3(1,2);

int main()
{
	...
}

c3便是所谓global object,其生命在整个程序结束之际才结束。你也可以把它视为一种static object,其作用域是整个程序


heap objects的生命期

class Complex { ... };
	...
{
	Complex* p = new Complex;
	...
	delete p;
}

p所指的便是heap object,其生命在它被delete之际结束

以下情况出现内存泄漏

class Complex {...};
	...
{
	Complex* p = new Complex;
}

以上出现内存泄漏(Memory leak),因为当作用域结束,p所指的heap object仍然存在,但指针p的生命却结束了,作用域之外再也看不到p(也就没机会delete p)


new:先分配memory,再调用构造函数ctor

以复数为例:

Complex* pc = new Complex(1,2);

编译器将new Complex()转化为

  • 第一步:分配内存
  • 第二步:转型
  • 第三步:调用构造函数
Conplex *pc;
	//第一步:分配内存  
	void* mem = operator new( sizeof(Complex) );//sizeof(Complex)大小为8,复数的数据类型为double(占8个字节),//operator new()其内部调用malloc(n)
	//第二步:转型
	pc = static_cast<Complex*>(mem);
	//构造函数的全名Complex::Complex(pc,1,2);  下面的构造函数的参数隐藏了pc
	pc -> Complex::Complex(1,2);//构造函数Complex()和类Complex
	//pc为上图两个double所在内存的起始位置

以字符串为例:

在这里插入图片描述

String* ps = new String("Hello");

编译器将new String()转化为

  • 第一步:分配内存
  • 第二步:转型
  • 第三步:调用构造函数
String* ps;
{
	//第一步:分配内存
	void* mem = operator new( sizeof(String) );//其内部调用malloc(n)
	//第二步:转型
	pc = static_cast<String*>(mem);
	//第三步:调用构造函数
	//构造函数的全名String::String(pc,"Hello");  下面的构造函数的参数隐藏了ps
	pc -> String::String("Hello");//构造函数String()和类Complex
	
}

delete:先调用析构函数dtor,再释放memory

  • delete和new过程相反

以复数为例:

Complex* pc = new Complex(1,2);
...
delete pc;

编译器将delete pc;转化为

  • 第一步:调用析构函数
  • 第二步:释放内存
//第一步:调用析构函数
Complex::~Complex(pc);
//第二步:释放内存
operator delete(pc);

以字符串为例:

String* ps = new String("Hello");
...
delete ps;

编译器将delete ps;转化为

  • 第一步:调用析构函数(清理数据)
  • 第二步:释放内存(清除占用空间)
//第一步:调用析构函数
String::~String(ps);//析构函数要把动态分配的空间删掉,至于字符串本身只是一个指针
//第二步:释放内存
operator delete(ps);

动态分配所得的内存块(Memory block)in VC(在VC中给的每一个内存块一定是16的倍数)

第一部分:复数

  • 大图中第一列(在Debug mode下)

    1.创建一个复数的实部虚部(2个double)得到 8 byte (绿色区域)

    2.调试模式下多得到 32 byte(complex上方)+4 byte(complex下方) (灰色区域)

    3.2个 cookie(一个cookie有 4 byte) (橘色区域)
    [复数8 byte+调试模式下(32+4)byte+上下两个cookie(4×2)=52,52不能整除16,填充为64]
    [free()和malloc()函数说好了,在内存块上下放2个cookie,这里64的16进制为40,红色区域记录值为41,40借用最后一位(bit),用0或1表示这一块内存是给出去还是收回来,这里对操作系统而言是给出去内存了,对程序而言是获得了内存,获得了内存区域,所以这里是41,这里最后一位是1,如果将这块内存区域还给操作系统则变为40,最后一位变为0]
    [为什么可以借1个bit使用呢?因为内存填充调整到16的倍数,16倍数的话最后4个bit都是0,所以可以借其中一位(bit)来用]

    4.填充 pad(不能整除16时,将内存填充为16的倍数) (蓝绿色区域)

  • 大图中第二列(在Release mode下)

    1.创建一个复数对象(实部虚部,2个 double)得到 8 byte (绿色区域)

    2.2个 cookie(记录整块内存大小)(一个cookie有 4byte) (橘色区域)
    [复数8 byte+上下两个cookie(4*2)=16,不需要填充pad]
    [16的十六进制表示是1,程序获得了内存区域,所以这里是11(最后一位为1)]

第二部分:字符串

  • 大图中第三列(在Debug mode下)

    1.创建一个字符串对象(字符串本身只内含一个指针,所以它的大小是指针的大小 4byte) (绿色区域)

    2.调试模式下多得到 32byte(complex上方)+4 byte(complex下方) (灰色区域)
    [String object 4 byte+(32+4)+(4*2)(2个cookie,1 cookie 个4 byte)=48,48是16的倍数,48对应的十六进制为3,所以不需要pad填充]

  • 大图中第四列(在Release mode下)

    1.创建一个字符串对象(字符串本身只内含一个指针,所以它的大小是指针的大小 4 byte) (绿色区域)

    2.2个cookie(记录整块内存大小)(一个cookie有 4 byte) (橘色区域)
    [4 byte + (4*2)byte = 12 byte,12不是16的倍数,所以需要pad填充]
    [填充4 byte(一小格)成为16 byte,16的十六进制是1(11的第一个1),程序获得内存区域最后一位为1(11的最后一个1),所以这里是11。如果把内存还给操作系统,11就会变成10]

    3.填充pad(不能整除16时,将内存填充为离当前内存值最近的 16的倍数) (蓝绿色区域)


动态分配所得的数组(Array)

第一部分:复数

  • 大图中第一列(在Debug mode下)
Complex* p = new Complex[3];


1.一个复数 (实部虚部)2个double(double 8 byte),这里数组大小为 3,内存大小8×3 (灰色区域)

2.调试模式下分配 32 byte(数组大小3的上方)+4 byte(数组大小3的下方) (橘色区域)

3.上下共2个cookie(4×2)(1个cookie 4byte) (白色区域51h,h为十六进制,80用十六进制表示为5,程序获得内存区域最后一位用1表示)

4.在VC中(其他的不一定是)图中有1格(4 byte)放数组大小,这里图中为数字3表示有3对double

5.(全部加起来为72 byte)pad填充到 80 (蓝绿色区域)

  • 大图中第二列(在Release mode下)
Complex* p = new Complex[3];


1.一个复数(实部虚部) 2个 double(double 8 byte),这里数组大小为3,内存大小 8×3 (灰色区域)

2.上下共 2个 cookie(4×2)(1个cookie 4byte) (白色区域31h,h为十六进制,3是48的十六进制表示,最后一位用1表示程序获得内存区域)

3.在VC中(其他的不一定是)图中有1格(4 byte)放数组大小,这里图中为数字 3表示有 3个复数(3对 double)

4.(全部加起来为 36 byte)pad填充到 48 (蓝绿色区域)

第二部分:字符串

  • 大图中第三列(在Debug mode下)
String* p = new String[3];

在这里插入图片描述
1.一个指针占一小格(1格4 byte),这里数组大小为 3,内存大小 4×3(3个指针) (灰色区域)

2.调试模式下分配 32 byte(数组大小3的上方)+4 byte(数组大小3的下方) (橘色区域)

3.上下共 2个 cookie(4×2)(1个cookie 4byte) (白色区域41h,h为十六进制,64的十六进制表示为4,程序获得内存区域,最后一位用1表示)

4.在VC中(其他的不一定是)图中有 1格(一格内有一个指针,4 byte)放数组大小,这里图中为数字 3表示3个字符

5.(全部加起来为60 byte)pad填充到 64 (蓝绿色区域)

  • 大图中第四列(在Release mode下)
String* p = new String[3];

在这里插入图片描述
1.一个指针占一小格(1格4 byte),这里数组大小为 3,内存大小 4×3(3个指针) (灰色区域)

2.上下共 2个 cookie(4×2)(1个cookie 4byte) (白色区域21h,h为十六进制,32的十六进制表示为2,程序获得内存区域,最后一位用1表示)

3.在VC中(其他的不一定是)图中有 1格(一格内有一个指针,4 byte)放数组大小,这里图中为数字 3表示3个字符


在字符串中array new 一定要搭配 array delete

  • array new (本例中 m_data = new char[strlen(cstr)+1] ; )
  • array delete(本例中delete[] m_data;//[ ] 表明要删除的是数组,而不是一个字符,唤起3次析构函数,把动态分配的内存删掉)

    内存泄漏的地方

    在字符串中array new 一定要搭配 array delete,像在复数中=因为它没有指针也不会做动态分配,即使你没有用array new和array delete也可以,因为复数它没有下图这种东西

    但要养成好习惯你要用array new就一定要搭配array delete保证万无一失

笔记来源:
https://www.bilibili.com/video/BV1aW411H7Xa?p=8

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Uncertainty!!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值