0%

浅谈内存管理的若干方式

前置知识科普

栈内存和堆内存

常识告诉我们,在计算机系统的内存之中,有一个系统栈和一个负责管理动态分配内存的系统堆,栈向地地址生长,而堆向高地址生长。这两个结构所对应的那部分内存区域分别称为栈内存和堆内存。

运行程序的时候,

生命周期

lifetime

RAII

RAII全称是Resource Acquisition Is Initialization(资源获取即初始化),这个概念是为了在面向对象的语言中更优雅地管理资源,这里的资源指的是像file、socket、mutex等这些传统以来需要open然后close或者需要先lock然后unlock的类型。

我们用比较简单的一句话概括RAII:资源与对象的生命周期高度绑定,资源在其所对应的对象完成初始化时得到获取,在资源生命周期走到尽头时得到释放与关闭。

传统的使用方式与RAII不同,资源的生命周期是需要额外考虑的。比如C打开文件是通过一个open函数拿到file descriptor,然后传这个descriptor调用read、write等函数,等到所有的读写使用结束后再由开发者显式地close;再比如java打开文件,经常要用try-catch-finally语句去管理,而且经常会在finally去显式close。在这种传统的面向过程的操作范式中,资源的获取与释放都需要开发者显式调用。而RAII在开发者的角度则不用去额外去管资源的生命周期,在多线程的并发运行之中也可以在编译期统一管理好,避免deadlock等等的问题。

堆内存的管理方式

全手动内存管理

C语言由于其历史特性,几乎是汇编的直接抽象表现,在内存管理方面也与汇编相似,内存分配与释放全靠开发者自身的使用,所以说可以说C是全手动内存管理的典范。

mallocrealloccalloc等函数用来在堆上分配内存,而free用来释放堆上已分配的内存。但是开发者所得到的只是一个指针(类型需要在malloc之外强转),没有任何生命周期的信息。并且,这块内存区域只会在调用free之后才得到释放,这意味着这块内存区域不仅可以在单个线程中跨scope使用,而且允许在不同线程中使用,这些问题如果开发者没有意识到,就很容易踩到race等等的大坑。而且更糟的是,在free掉之后,原来储存着地址的那个指针仍可能被使用,这直接造成了UAF等影响巨大的问题。

在这种条件下,程序是否安全要全靠开发者,开发者的疏忽很容易导致非预期运行错误的产生,编译期错误并不能足够地规避问题。

C++虽然向下兼容C,使用了新的newdelete关键字,但核心问题还是没得到解决。这个核心问题就是指针满天飞所造成的生命周期混乱的问题,而C和C++如果只使用C-Type指针的话都没有办法在编译时完成查错,生命周期混乱编译器睁一只眼闭一只眼就过去了,所以只能比较大程度上寄托于运行时发生非预期现象才能发现。

C++认识到向下兼容C所导致的这个问题,之后C++在RAII这方面做出了一些实践,类似std::stringstd::vector等的动态内存容器类型和std::smart_ptrstd::unique_ptr等等的智能指针类型自然是被官方强推,已经是现代C++的基础数据类型了。

GC内存管理

Garbage Collector简称GC,是以Java等语言所采用的内存管理方式的统称。

GC的好处是开发者可以不用全手动完成内存管理,不需要手动释放内存(一般也不会允许),GC会在运行的过程中帮你完成。

(暂时停更,有时间再补充。。。)