前置知识科普
栈内存和堆内存
常识告诉我们,在计算机系统的内存之中,有一个系统栈和一个负责管理动态分配内存的系统堆,栈向地地址生长,而堆向高地址生长。这两个结构所对应的那部分内存区域分别称为栈内存和堆内存。
运行程序的时候,
生命周期
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是全手动内存管理的典范。
malloc
、realloc
、calloc
等函数用来在堆上分配内存,而free
用来释放堆上已分配的内存。但是开发者所得到的只是一个指针(类型需要在malloc
之外强转),没有任何生命周期的信息。并且,这块内存区域只会在调用free
之后才得到释放,这意味着这块内存区域不仅可以在单个线程中跨scope使用,而且允许在不同线程中使用,这些问题如果开发者没有意识到,就很容易踩到race等等的大坑。而且更糟的是,在free
掉之后,原来储存着地址的那个指针仍可能被使用,这直接造成了UAF等影响巨大的问题。
在这种条件下,程序是否安全要全靠开发者,开发者的疏忽很容易导致非预期运行错误的产生,编译期错误并不能足够地规避问题。
C++虽然向下兼容C,使用了新的new
和delete
关键字,但核心问题还是没得到解决。这个核心问题就是指针满天飞所造成的生命周期混乱的问题,而C和C++如果只使用C-Type指针的话都没有办法在编译时完成查错,生命周期混乱编译器睁一只眼闭一只眼就过去了,所以只能比较大程度上寄托于运行时发生非预期现象才能发现。
C++认识到向下兼容C所导致的这个问题,之后C++在RAII这方面做出了一些实践,类似std::string
、std::vector
等的动态内存容器类型和std::smart_ptr
、std::unique_ptr
等等的智能指针类型自然是被官方强推,已经是现代C++的基础数据类型了。
GC内存管理
Garbage Collector简称GC,是以Java等语言所采用的内存管理方式的统称。
GC的好处是开发者可以不用全手动完成内存管理,不需要手动释放内存(一般也不会允许),GC会在运行的过程中帮你完成。
(暂时停更,有时间再补充。。。)