概观
简而言之,
C++
中存在着如下两个流行的内存管理策略:
其一是通用意图(
general-purpose
)的分配策略,它可以提供调用方的任意大小(称为请求大小或块大小)的内存块,通用意图的分配策略非常灵活,但也有几个缺点,其中两个就是:(
a
)性能,因为这种分配策略需要做更多的工作;
(b)
碎片,因为随着内存块的分配和释放,最终可能导致许多小的,不连续的未分配的内存区域。
其二是固定大小(
fixed-size
)的分配策略,它总是返回固定大小的内存块,着显然不象通用意图的分配策略那样灵活,但它的好处是可以在得多的时间内完成分配,而且不会产生那样的内存碎片。
第三种主要就是垃圾收集,该策略与
C/C++
指针
,malloc
和
new
并不完全兼容,因此与我们当前讨论的主题不直接相关;然而,垃圾收集技术正在变得越来越流行,而且正在走进
C++
,只不过不是跟指针和
new
搭配。
时间上,我们通常会见到这些策略是被组合在一起运用的。例如,或许某些内存管理器对任何大于
S
的内存请求使用的都是通用意图的分配,而对小于等于
S
的请求册一律采用固定大小的分配策略以获得优化的效果。一般来说,为大小是
1
,是
2
,是
3
等等的内存请求全都特殊对待、的做法是不实用的,所以内存管理器一般会机遇某个特定大小(如
16
字节)的倍数维护一个个的所谓仓库(
arena
)
,
譬如,现请求
16
字节的内存,那么很好,因为恰好只使用
16
字节;但若请求
17
字节的内存,这块内存就会从“存放”着
32
位字节大小的内存块的
arena
中分配出来,即浪费了
15
字节。这是内存管理策略可能导致的额外开销的来源之一。
划分策略
在
C++
标准库异己使用了
C++
标准库实现的典型环境中存放在有好几种层次的内存管理,其中每层都可能回覆盖原先的(较低的)层面:
首先是操作系统内核提供了最为基础的内存反陪服务。这个底层的分配策略及其特性可能根据操作系统平台的不同而有很大差异,而且这一层内存管理是最可能受到硬件相关的考虑所影响的。
编译器的默认运行时库也回建立起它自己的内存分配服务,譬如
C++
中的
opterator new
和
C
中的
malloc
,这一层是建立在操作系统的本地分配服务基础上的,可能只是后者的一层薄薄的外衣,继承了其特性;也有可能通过先从后者那里“买进”大块内存
(chunk),
然后再切割转手分配出去的方式来覆盖操作系统的本地内存分配服务,至于如何切割“买来的”大块内存就是由这一层运行时库自己自由选择的了。
而标准库容器和标准分配器进而利用(也许覆盖)运行时库提供的服务来实现它们自己的策略和优化。
最后,用户自己定义的同期或用户自定义的分配器可能会进一步复用上面提到的任何一层服务(例如,若可移植性并不呢们重要,它们可能会想要直接访问本地分配服务),并按照它们自己的策略和优化
因此,内存分配器有好些不同的形式和种类,而不同的操作系统上描绘相同的操作系统上的不同编译器,或不同容器之间都会有所不同,甚至即便是在不同的对象之间也会有所不同
准则:弄清楚谁负责做什么;弄清楚自己当前的平台和标准库所使用的实际(且系统相关的)分配策略以及他们各自的职责是什么。
现代操作系统撒谎功能的内存管理就其本身来说可能是复杂而成熟的,然而充其量它也不过是(仅仅)一层(值得
C++
程序员关心的)内存管理层而已。标准库则提供了更多层次的让我们选择;首先是它的用于分配和归还的基本设施,然后是它的标准容器和分配器,再然后就是自己编写的名目繁多的自定义容器了。