未使用的 STL 容器是否分配内存

Does an unused STL container allocate memory?

本文关键字:是否 分配 内存 STL 未使用      更新时间:2023-10-16

给定代码:

class Foo {
  std::vector<int> items;
  std::map<int, int> dictionary;
};
  1. 如果上面的向量或映射中没有添加任何内容,是否仍会分配缓冲区内存块? (换句话说,缓冲区分配是否总是在容器创建期间发生,或者是否可以推迟到调用 push_back 等函数?

  2. 是否有处理初始 STL 容器缓冲区分配计时的标准,或者是否允许该行为在 STL 容器和编译器之间变化?

注意:这个问题不是关于此类容器会增加类 Foo 大小的额外字节。

(此问题的一个强调分配大小的相关子集是向量的初始容量(以 C++为单位)。

C++引用 对于 C++17,默认构造函数noexcept,如果分配器构造noexcept。所以这取决于使用的分配器。在VS 2015中,标准构造函数是noexcept

澄清:这意味着如果分配器没有noexcept则不会分配内存块。

对于你的第二个问题:相同的引用,它是O(1)。

Standard 对此没有任何说明,但我专门研究的实现会为 std::vector 做一些预置,并且不会为std::map预先分配任何东西。

这实际上曾经对我打击很大,当我拥有一个巨大的容器时,其中元素具有微小的 - 不超过 10 个元素,大多数条目都有 0 大小的向量 - 向量。此实现中的默认矢量容量为 32,而"32 * sizeof(vector_element) * number_of_elements"恰好非常大。

如前所述,这没有很好的定义。但是,您可以对其进行测试。

例如使用 gcc/linux。制作一个简单的程序,用-O0 -g编译并在gdb中运行它。然后

break main
run
break malloc
cont

现在只需在每个 malloc 上运行backtrace,您将看到动态分配。使用我的 gcc 5.3.0,两个空容器都不会分配堆内存,这是在第一个 push_back/operator[] 上完成的。

当然,您应该使用首选的调试器并中断分配器底层函数,如果这不是gdb/malloc

现在,如果您考虑这两种情况。在这种情况下,预先分配内存是否有意义?

std::vector<int> foo;
foo.push_back(13);

好吧,从技术上讲,您可能会保存nullptr的检查,但是使用将向量实现为3指针的常用方法,不需要额外的检查。

但请考虑

std::vector<int> foo;
foo.reserve(100);

在这种情况下,预先分配将不利于性能。

我找不到为地图等树结构进行预分配的论据。

请记住,这是一个非常具体的优化。只有在有充分理由的情况下才能对此进行优化(基准测试!

注意:您可能想阅读有关小字符串优化的信息,这是一种非常常见的技术,相关但不同。

  1. 如果上面的向量或地图中没有添加任何内容,是否仍会为潜在条目分配内存块?(换句话说,条目分配是否总是在容器创建期间发生,或者是否可以推迟到调用 push_back 等函数?

这是可能发生的,是的。它实际上是容器实现的细节,未在标准中指定。

  1. 是否有处理初始 STL 容器分配时间的标准,或者是否允许该行为在 STL 容器和编译器之间变化?

例如,您可以使用成员的std::unique_ptr来延迟创建,并通过调用 getter 函数来创建它们。

值得注意的是

,Microsoft的 STL 实现目前确实在默认构造函数中分配了 std::mapstd::set 。Godbolt 示例 - 请注意程序集输出第 8 行对 operator new 的调用。

是的,这绝对会影响性能。我只是在分析了一些神秘的慢代码之后才意识到这一点,当时事实证明,具有很少使用的map成员的类的默认构造函数正在主导相关循环的运行时。

如果像我一样,你对这个实现决定并不完全兴奋,我会指出 Boost.Container 的对应方确实保证了零分配默认构造。