【问题标题】:Why is sizeof(string) == 32?为什么 sizeof(string) == 32?
【发布时间】:2010-09-22 15:19:25
【问题描述】:

导致 sizeof() 为 32 的字符串结构中的开销是什么?

【问题讨论】:

  • 如果您打开平台的<string> 标头,您可以确切地看到为什么std::string 是那个大小。 @Queso:sizeof 产生对象的大小以字节为单位
  • 如果 sizeof 返回指针中的位数,那么您的编译器已损坏
  • @Martin:因为“湿度”几乎被定义为水(或任何液体)的属性。我不知道“32”被定义为字符串的大小。
  • @Steve Jessop:由于当前的实施,水是湿的(地球作为 STP 允许水变成液体)。在其他实现中,它不是湿的(就像木星一样,它是一种气体)。所以这个字符串实现是 32,因为这是它在这个实现中的构建方式,在其他实现中它将是 16,在另一个实现中是 64。字符串的大小(如水)取决于它所使用的环境。
  • 好的,由于地球的 STP,水是液态的,我们可以通过查看影响它的因素来进一步钻探(例如,大气压力受质量和气体排放的影响)。那么问为什么一组实施者选择32,另一组选择64,与问为什么地球有一定的表面压力和温度有什么关系?一种是众生的选择。另一个不是 IMO,但即使是 IYO,我认为 C++ 实现者也不会像上帝那样声称不可言喻。

标签: c++ string


【解决方案1】:

大多数现代std::string 实现1 将非常小的字符串直接保存在堆栈中的静态大小的char 数组中,而不是使用动态堆存储。这称为Small (or Short) String Optimisation (SSO)。它允许实现避免小字符串对象的堆分配并提高引用的局部性。

此外,还有一个std::size_t 成员来保存字符串大小和一个指向实际char 存储空间的指针。

具体实现的方式有所不同,但以下几行是可行的:

template <typename T>
struct basic_string {
    char* begin_;
    size_t size_;
    union {
        size_t capacity_;
        char sso_buffer[16];
    };
};

sizeof (void*) = 8 的典型架构上,这给了我们 32 字节的总大小。


1 “三巨头”(GCC 的 libstdc++ 版本 5,Clang 的 libc++ 和 MSVC 的实现)都做到了。其他人也可以。

【讨论】:

  • @KonradRudolph 非常小的字符串直接保存到对象中,这可以是堆栈也可以是堆,具体取决于字符串本身的分配位置,不是吗?
  • @ManuelSelva 完全正确。
  • @KonradRudolph 如何强制字符串总是堆分配? (为了让字符串对象小于 32 字节,例如 8 字节。)
  • @LukeFisk-Lennon 你不能。小字符串优化是某些(嗯,所有现代的)标准库实现的实现细节,它不是由语言指定的。因此,您无法在 C++ 中更改它。您也不能在 C++ 之外更改它(例如通过编译器选项),因为这样的更改将是 ABI breaking。也就是说,GCC4 没有执行小字符串优化,因此原则上您可以使用--with-default-libstdcxx-abi=gcc4-compatible 配置您的 GCC,但这是一个糟糕的想法(= 非常古老的实现)。
  • @KonradRudolph 好的,我明白了。感谢您的快速回复。
【解决方案2】:

std::string 通常包含一个用于“小字符串优化”的缓冲区 --- 如果字符串小于缓冲区大小,则不需要堆分配。

【讨论】:

  • “通常”==“在 Windows 上”;-)
  • Windows 编译器并不是唯一进行小字符串优化的编译器
  • 当然,但是如果您不愿意命名它们,那么很难判断这是否是“典型”行为,或者只是因为这是常见实现的行为(以及大概是其他人)。
  • 据我了解,Dinkumware 和 STLPort 都可以,但 gcc 的实现没有。
  • 顺便说一句,我提到它是因为“通常”的范围从“我有理由相信你永远不会看到其他任何东西”到“我使用的 50% 或更多的实现都可以这”。我认为这很容易被误解。无论是这种优化,还是没有优化,都不应被视为异常。
【解决方案3】:

我的猜测是:

class vector
{
    char type;
    struct Heap
    {
      char*   start;
      char*   end;
      char*   allocatedEnd;
    };
    struct Stack
    {
      char    size;
      char    data[27];
    }
    union
    {
        Stack   stackVersion;
        Heap    heapVersion;
    } version;
};

但我敢打赌,有数百种方法可以做到这一点。

【讨论】:

  • awww...没有引用计数?折叠怎么了?
  • @ErikAronesty 有一个阶段尝试使用 std::string 进行引用计数,但很明显他的效率不是很高(多年来有几篇论文),而是短字符串优化变得流行起来。
【解决方案4】:

在 g++5.2 中(例如 g++4.9,它是不同的)字符串基本上定义为:

class string {
  char* bufferp;
  size_t length;
  union {
    char local_buffer[16];
    size_t capacity;
  };
};

在普通计算机上,这加起来是 32 个字节 (8+8+16)。

实际定义当然是

typedef basic_string<char> string;

但想法是一样的。

【讨论】:

    【解决方案5】:

    它依赖于库。您不应该依赖std::string 对象的大小,因为它可能会在不同的环境中发生变化(显然在不同的标准库供应商之间,但也在同一库的不同版本之间)。

    请记住,std::string 实现是由针对各种用例进行优化的人员编写的,通常会导致 2 种内部表示,一种用于短字符串(小型内部缓冲区),另一种用于长字符串(堆分配外部缓冲器)。开销与在每个 std::string 对象中保存这两者有关。

    【讨论】:

      【解决方案6】:

      问:为什么狗是黄色的? A:不一定。

      (一个?)std::string 对象的大小取决于实现。我刚刚检查了 MS VC++ 2010。它确实为 std::string 使用了 32 个字节。有一个 16 字节的联合,其中包含字符串的文本(如果合适的话)或指向堆存储的指针以存储更长的字符串。如果实现者选择在字符串对象而不是堆中保留 18 字节字符串,则大小将为 34 字节。其他 16 个字节构成开销,包括字符串长度和当前为字符串分配的内存量等内容。

      不同的实现可能总是从堆中分配内存。这样的实现无疑需要更少的内存来存储字符串对象。

      【讨论】:

        猜你喜欢
        • 2015-08-21
        • 2013-09-24
        • 2019-04-22
        • 2019-08-17
        • 2012-06-14
        • 2011-03-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多