std::allocator
是底层内存模型的抽象,它包含调用new
和delete
的功能。 delete
不需要大小,但deallocate() 需要。
void deallocate(T * p,std :: size_t n);
"参数n必须等于调用的第一个参数 最初生成p的allocate();否则,行为是 。未定义"
为什么呢?
现在我要么必须在解除分配之前进行额外的计算,要么开始存储我传递给分配的大小。如果我没有使用分配器,我就不必这样做。
答案 0 :(得分:23)
std::allocator
API的设计 - Allocator
concept - 旨在促进潜在替代工作。
std::allocator
是对底层内存模型的抽象
它不是必须的!通常,分配器不需要使用C malloc
和free
,也不需要使用delete
或非就地new
。是的,默认通常会这样做,但分配器机制并不仅仅是对C内存模型的抽象。不同之处通常是自定义分配器的全部目的。请记住,分配器是可替换的:特定的std::allocator
可能不需要释放的大小,但任何替换都可能。
std::allocator
的合规实现可以自由断言您确实将正确的n
传递给了deallocate
,否则将取决于正确的大小。
恰好malloc
和free
将块大小存储在其数据结构中。但一般来说,分配器可能不会这样做,并且要求它这样做是过早的悲观化。假设您有一个自定义池分配器并且正在分配int
个块。在典型的64位系统上,存储64位size_t
和32位int
的开销是200%。分配器的用户可以更好地在分配中存储大小,或者以更便宜的方式确定大小。
好的malloc实现不会为每个小分配存储分配大小;它们并且能够从指针本身导出块大小,例如通过从块指针派生块指针,然后检查块头的块大小。这当然是一个细节。您可以使用特定于平台的API获取大小的下限,例如OS X上的malloc_size
,Windows上的_msize
,Linux上的malloc_usable_size
。
答案 1 :(得分:1)
内存分配算法通常很有用,可以最大限度地减少所需的开销。一些跟踪空闲区域而不是分配区域的算法可以将开销总量减少到低常量值,每块开销为零(簿记信息完全存储在空闲区域内)。在使用此类算法的系统上,分配请求会从空闲池中删除存储,而取消分配请求会将存储添加到空闲池中。
如果使用池的连续区域满足256和768字节的分配请求,则内存管理器状态将与使用相同区域满足两个512字节请求的情况相同。如果内存管理器传递了一个指向第一个块的指针并要求释放它,它将无法知道第一个请求是用于256个字节,还是512个字节,或任何其他数字,因此无法知道应该将多少内存添加回池中。
实施" malloc"并且"免费"在这样的系统上,它需要在每个块的存储区域的开头存储每个块的长度,并返回一个指向下一个适当对齐的地址的指针,该地址在该长度之后可用。实现这样做当然可能,但它会为每个分配增加4-8个字节的开销。如果调用者可以告诉释放例程将多少存储空间添加回内存池,则可以消除这种开销。
答案 2 :(得分:0)
此外,很容易适应这个设计点:只需将事物分配为struct
,并将大小存储为struct
中的元素。调用deallocator的代码现在知道要提供什么值,因为结构本身包含它。
通过这样做,你实际上正在做任何其他语言实现可能慷慨地 你。你只是明确地做同样的事情。
现在,鉴于我们正在谈论 C ++ ,其中已经内置了大量容器类,我坦率地鼓励你避免“滚动你自己”,如果你可以避免它。只是找到一种方法来使用语言和标准库已经提供的一个漂亮的容器类。
否则,请确保将您正在构建的内容打包为本地容器类。确保处理分配器和解除分配器的逻辑在程序中只出现一次。 (即,在这个类中。)用专门设计用于检测错误的逻辑慷慨地洒上它。 (例如,在分配对象时插入到对象中的sentinel值,并且必须在对象被释放时找到,并且在它之前被擦除。显式检查存储的size-value确定它是有道理的。等等。)
答案 3 :(得分:-1)
您没有必需来跟踪大小。标准分配器不跟踪大小,因为它假设所有分配的大小。当然,有不同类型的分配器用于不同的目的。您可能已经猜到,块大小分配器具有固定大小。某些应用程序(如视频游戏)预先预先分配内存,并消除了需要跟踪每个分配的大小的开销。
标准库尝试尽可能为通用。有些分配器需要跟踪大小,有些则不需要,但是所有分配器都需要符合接口。
答案 4 :(得分:-3)
我没有确凿的证据,但我的直觉是,分配器不需要使用C ++ operator new / delete,也可能使用无法分配数组并知道其大小的内存管理例程 - 比如malloc,例如。