为什么 std::allocator::deallocate 需要尺寸?

why does std::allocator::deallocate require a size?

std::allocator 是对底层内存模型的抽象,它包装了调用 newdelete 的功能。 delete 虽然不需要尺寸,但 deallocate() 需要 它。

void deallocate( T* p, std::size_t n );
"The argument n must be equal to the first argument of the call to allocate() that originally produced p; otherwise, the behavior is undefined."

为什么?

现在我要么必须在解除分配之前进行额外的计算,要么开始存储我传递给分配的大小。如果我不使用分配器,我就不必这样做。

我没有确凿的证据,但我的直觉是分配器不需要使用 C++ 运算符 new/delete 并且还不如使用不具备分配数组和知道它们的大小——例如 malloc。

std::allocatorAPI-Allocator concept-的设计是为了方便潜在的替代工作。

std::allocator is an abstraction over the underlying memory model

不一定!通常,分配器不需要使用 C mallocfree,也不需要 delete 或非就地 new。是的,default 通常会这样做,但分配器机制不仅仅是对 C 内存模型的抽象。与众不同通常是自定义分配器的全部目的。请记住,分配器是可替换的:特定的 std::allocator 可能不需要释放大小,但任何替换都可能需要。

std::allocator 的兼容实现可以自由断言您确实将正确的 n 传递给 deallocate,否则取决于大小是否正确。

碰巧mallocfree将块大小存储在其数据结构中。但一般来说,分配器可能不会这样做,要求它这样做是过早的悲观情绪。假设您有一个自定义池分配器,并且正在分配 int 块。在典型的 64 位系统上,存储 64 位 size_t 和 32 位 int 的开销是 200%。分配器的用户可以更好地在分配中存储大小,或者以更便宜的方式确定大小。

好的 malloc 实现不会为每个小分配存储分配大小;他们并且能够从指针本身导出块大小,例如通过从块指针导出块指针,然后检查块头的块大小。这当然只是一个细节。您可以使用特定于平台的 APIs 获得大小的下限,例如 Linux.

上的 malloc_size on OS X, _msize on Windows, malloc_usable_size

不需要记录尺码。标准分配器不跟踪大小,因为它假定所有分配的大小。当然,有不同类型的分配器用于不同的目的。正如您可能已经猜到的那样,块大小分配器具有固定大小。一些应用程序(如视频游戏)会预先预先分配内存,并消除需要跟踪每次分配大小的开销。

标准库尽量通用。有些分配器需要跟踪大小,有些则不需要,但所有分配器都需要符合接口。

此外,很容易适应这个设计点:只需将事物分配为 struct,并将大小存储为 struct 中的一个元素。调用解除分配器的代码现在知道要提供什么值,因为结构本身包含它。

通过这样做,您实际上正在做任何其他语言实现可能正在为您 慷慨地做的事情。你只是在明确地做同样的事情。

现在,鉴于我们正在谈论 C++,它已经内置了大量很棒的容器-classes,我会坦率地鼓励您避免"rolling your own" 如果你能避免的话。只需找到一种方法来使用语言和标准库已经提供的漂亮容器之一-classes。

否则,请务必将您在此处构建的内容 打包为本地容器​​ class。 确保处理分配器和释放器在你的程序中只出现一次。 (即,在此 class 中。)用专门设计用于检测错误的逻辑慷慨地散布它。 (例如,在分配对象时插入到对象中的标记值,必须在对象释放时找到,并且在它释放之前被擦除。显式检查存储的大小值以进行确保它有意义。等等。)

内存分配算法通常有助于最大限度地减少所需的开销。一些跟踪空闲区域而不是分配区域的算法可以将总开销减少到一个较低的常数值,每块开销为零(簿记信息完全存储在空闲区域中)。在使用此类算法的系统上,分配请求会从空闲池中删除存储,而取消分配请求会向空闲池添加存储。

如果使用池的连续区域满足 256 和 768 字节的分配请求,则内存管理器状态将与使用同一区域满足两个 512 字节请求时的状态相同。如果向内存管理器传递一个指向第一个块的指针并要求释放它,它将无法知道第一个请求是 256 字节、512 字节还是任何其他数字,因此无法知道应将多少内存添加回池中。

在这样的系统上实现 "malloc" 和 "free" 需要将每个块的长度存储在其存储区域的开头,并 return 指向下一个块的指针在该长度之后可用的适当对齐的地址。一个实现当然可以这样做,但它会为每个分配增加 4-8 字节的开销。如果调用者可以告诉释放例程要将多少存储添加回内存池,就可以消除这种开销。