当前位置: 首页 > 知识库问答 >
问题:

为什么libc的std::string实现占用的内存是libstdc的3倍?

晏晨朗
2023-03-14

考虑以下测试程序:

#include <iostream>
#include <string>
#include <vector>

int main()
{
    std::cout << sizeof(std::string("hi")) << " ";
    std::string a[10];
    std::cout << sizeof(a) << " ";
    std::vector<std::string> v(10);
    std::cout << sizeof(v) + sizeof(std::string) * v.capacity() << "\n";
}

libstdc和libc的输出分别为:

8 80 104
24 240 264

如您所见,libc占用的内存是一个简单程序的3倍。导致这种内存差异的实现有何不同?我需要担心吗?我如何解决它?

共有3个答案

郑西岭
2023-03-14

摘要:看起来libstdc只使用了一个char*。事实上,它分配了更多的内存。

因此,您不应该担心Clang的libc实现内存效率低下。

根据libstdc的文件(详细描述):

A string looks like this:

                                        [_Rep]
                                        _M_length
   [basic_string<char_type>]            _M_capacity
   _M_dataplus                          _M_refcount
   _M_p ---------------->               unnamed array of char_type

其中,M p指向字符串中的第一个字符,将其转换为指向-u Rep的指针,然后减去1以获得指向标头的指针。

这种方法具有巨大的优势,一个字符串对象只需要一次分配。所有的丑陋都局限在一对内联函数中,每个内联函数都编译成一条add指令:_Rep::u M_data()和字符串::u M_Rep();以及分配函数,该函数获取一块原始字节并具有足够的空间,并在前端构造一个_Rep对象。

您希望\u M\u数据指向字符数组而不是\u Rep的原因是,调试器可以看到字符串内容。(可能我们应该添加一个非内联成员来获取调试器要使用的\u Rep,这样用户就可以检查实际的字符串长度。)

因此,它看起来就像一个char*,但这在内存使用方面具有误导性。

以前,libstdc主要使用这种布局:

  struct _Rep_base
  {
    size_type               _M_length;
    size_type               _M_capacity;
    _Atomic_word            _M_refcount;
  };

这更接近libc的结果。

libc使用了"短字符串优化",确切的布局取决于是否定义了_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT,如果定义了,如果字符串短,数据指针将是字对齐的,详情见源代码。

短字符串优化避免了堆分配,因此如果只考虑堆栈上分配的部分,它看起来比libstdc实现更昂贵。sizeof(std::string)仅显示堆栈使用情况,而不是整体内存使用情况(堆栈堆)。

相旭
2023-03-14

您不必担心,标准库实现者知道他们在做什么。

使用来自GCC subversion中继的最新代码libstdc给出了这些数字:

32 320 344

这是因为几周前,我切换了默认的std::string实现,以使用小字符串优化(空间为15个字符),而不是您测试的写时复制实现。

符佐
2023-03-14

这里有一个简短的程序,可以帮助您探索std::string的两种内存使用:堆栈和堆。

#include <string>
#include <new>
#include <cstdio>
#include <cstdlib>

std::size_t allocated = 0;

void* operator new (size_t sz)
{
    void* p = std::malloc(sz);
    allocated += sz;
    return p;
}

void operator delete(void* p) noexcept
{
    return std::free(p);
}

int
main()
{
    allocated = 0;
    std::string s("hi");
    std::printf("stack space = %zu, heap space = %zu, capacity = %zu\n",
     sizeof(s), allocated, s.capacity());
}

使用http://melpon.org/wandbox/很容易获得不同编译器/库组合的输出,例如:

gcc 4.9.1:

stack space = 8, heap space = 27, capacity = 2

gcc 5.0.0:

stack space = 32, heap space = 0, capacity = 15

clang/libc:

stack space = 24, heap space = 0, capacity = 22

VS-2015:

stack space = 32, heap space = 0, capacity = 15

(最后一行来自http://webcompiler.cloudapp.net)

上面的输出还显示了容量,这是衡量字符串在必须从堆中分配新的、更大的缓冲区之前可以容纳多少个字符。对于gcc-5.0、libc和VS-2015实现,这是对短字符串缓冲区的度量。也就是说,在堆栈上分配的大小缓冲区用于容纳短字符串,从而避免了更昂贵的堆分配。

在短字符串实现中,libc实现的(堆栈使用率)似乎最小,但包含最大的短字符串缓冲区。如果计算总内存使用量(堆栈堆),在所有4种实现中,libc对这个2字符字符串的总内存使用量最小。

应该注意的是,所有这些测量都是在64位平台上进行的。在32位上,libc堆栈的使用率将下降到12,小字符串缓冲区将下降到10。我不知道32位平台上其他实现的行为,但您可以使用上面的代码来了解。

 类似资料:
  • 考虑以下代码片段: > 带有的G9未能编译代码,错误如下: /opt/compiler explorer/gcc-trunk-20180711/include/c/9.0.0/variant:94:29:错误:嵌套名称说明符中使用的类型“std::variant_size”不完整 godbolt上的活生生的例子。组织 > 如果不是,这里什么实现是正确的,为什么?

  • 问题内容: 我正在使用c / c 为osx和linux开发命令行界面可执行文件。该项目将链接到opencv。我应该使用libc 还是libstdc ++? 问题答案: 我会为每个操作系统使用本机库,即GNU / Linux上的libstdc 和Mac OS X上的libc 。 libc 在GNU / Linux上不是100%完整的,而libstdc 更完整时使用libc并没有真正的优势。另外,如果

  • 问题内容: 这最终会消耗我所有的可用内存,然后进程被杀死。我曾尝试将标签从更改为“较小”标签,但这并没有什么不同。 我在做什么错/如何处理这个大文件? 我可以轻松地将其切碎并以较小的块进行处理,但这比我想要的还要难看。 问题答案: 当遍历整个文件时,将构建一棵树,并且不会释放任何元素。这样做的好处是元素可以记住其父元素是谁,并且您可以形成引用祖先元素的XPath。缺点是它会消耗大量内存。 为了在解

  • 我的问题作为标题,通过搜索得到了一些知识: > Linux具有共享内存如何测量应用程序或进程的实际内存使用情况? JVM将保留在Xms中设置的内存量,对于堆内存,-Xms JVM是什么意思? 下面是我在Ubuntu12.04(64bit)JDK 1.7.0_04上的测试运行。和顶部显示如下:

  • P0137 引入了函数模板 ,并在有关联合、生存期和指针的部分中对标准进行了许多更改。 这篇论文解决的问题是什么?我需要注意的语言变化是什么?我们在清洗什么?

  • 本文向大家介绍String 为什 么是不可变的?相关面试题,主要包含被问及String 为什 么是不可变的?时的应答技巧和注意事项,需要的朋友参考一下 简单的来说:String 类中使用 final 关键字修饰字符数组来保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 Ab