堆/栈/RAII

一、基本概念

英文heap,指动态分配内存的区域,不同于数据结构的堆。这里的内存分配后,需要手工释放,否则造成内存泄露。

c++里一个相关概念叫自由存储区,英文为free store,特指用new和delete来分配和释放内存的区域。一般而言,这是堆的一个子集:

new和delete操作的区域是free store

malloc和free操作的区域是堆

但 new 和 delete 通常底层使用 malloc 和 free 来实现,所以 free store 也是 heap。鉴于对其区分的实际意义并不大,所以根据参考资料先统一为

stack,指函数调用过程中产生的本地变量和调用数据的区域。与数据结构的栈高度相似,满足先进后出

RAII

Resource Acquisition Is Initialization,是C++特有的资源管理方式。C++依赖RAII做资源管理。

RAII依托栈和析构函数,来对所有的资源(包括堆内存在内)进行管理。

对RAII的使用,使得C++不需要Java那样的垃圾收集方法,也能有效地对内存进行管理。

导致在堆上分配内存(并构造对象)

1
2
3
4
5
6
7
8
//c++
auto ptr=new std::vector<int>();

//Java
ArrayList<int> list = new ArrayList<int>();

//python
lst=list()

程序通常需要牵涉三个可能的内存管理器的操作:

1.让内存管理器分配一个某个大小的内存块

2….释放一个之前分配的内存块

3….进行垃圾收集操作,寻找不再使用的内存块并予以释放

C++一般做操作1,2;JAVA做1,3;python做1,2,3;

第一,分配内存需要考虑程序当前已经有多少 未分配 内存。内存不足,要从操作系统申请新内存;内存充足时,从可用内存里取出一块合适大小的内存,标记为已用,将其返回给要求内存的代码

第二,释放内存,除了内存标记为未使用后,还要合并连续未使用的内存块成一块。

第三,垃圾收集操作,c++不用,不开展。

内存碎片化的情况,开发人员一般不用考虑。内存分配和释放的管理是内存管理器的任务。所以我们只需要正确使用new和delete即可。

漏掉delete会造成内存泄露。

c++更常见更合理的情况是,分配和释放不在一个函数里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bar* make_bar(...)
{
...
try{
bar* ptr=new bar();
...
}
catch(...){
delete ptr;
throw;
}
return ptr;
}

void foo()
{
...
bar* ptr=make_bar(...)
...
delete ptr;
}

这样导致的是漏delete的可能性变大

二、栈变化

讨论下c++函数调用、本地变量如何使用栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void foo(int n)
{

}
void bar(int n)
{
int a = n + 1;
foo(a);
}
int main()
{

bar(42);

}

该示例中,栈向上增长,大部分计算机体系架构中,栈的增长方向为低地址,上方意味着低地址。

栈的分配与释放看似都是移动了栈指针,由于先进后从出,一进一处,不会出现内存碎片

上图中每种颜色都表示某个函数占用的栈空间。叫做栈帧

编译器会自动调用析构函数,包括在函数执行发生异常的情况,在发生异常时对析构函数的调用,称为 栈展开

栈展开代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
class Obj {
public:
Obj() { puts("Obj()"); }
~
Obj() { puts("~Obj()"); }
};
void foo(int n)
{
Obj obj;
if (n == 42)
throw "It's yuleiyun";
}
int main()
{
try {
foo(41);
foo(42);
}
catch (const char* s) {
puts(s);
}
}

Obj()
~Obj()
Obj()
~Obj()
It’s yuleiyun

PS: puts只用于输出字符串,且换行

C++ 支持将对象存储在栈上面。但是,在很多情况下,对象不能,或不应该,存储在栈上。

比如:

对象很大;
对象的大小在编译时不能确定;
对象是函数的返回值,但由于特殊的原因,不应使用对象的值返回。

。。。。

如果返回类型是 shape,实际却返回一个 circle,编译器不会报错,但结果多半是错的。这种现象叫对象切片(object slicing)

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2023-2025 是羽泪云诶
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信