为什么sizeof(string)等于32?

41
字符串结构中的开销导致sizeof()大小为32,具体是什么呢?

5
如果你打开你的平台的<string>头文件,你就可以准确地看到为什么std::string的大小是那个大小。@Queso:sizeof返回一个对象占用的字节数。 - James McNellis
3
如果sizeof返回指针中的位数,那么你的编译器有问题。 - Anthony Williams
2
@Martin:因为“湿度”基本上被定义为水(或任何液体)的属性。我不知道“32”被定义为字符串的大小。 - Steve Jessop
15
@Steve Jessop:水是潮湿的,因为当前实现了地球作为一个标准温度和压力下(STP),使水能够保持液态。在其他实现中,它不是潮湿的(比如在气态的木星)。因此,这个字符串实现是32,因为在这个实现中它是这样构建的,在其他实现中可能是16或者64。字符串大小(就像水一样)将取决于它所用的环境。 - Martin York
3
好的,因为地球的标准大气压,水才是液态的,我们可以通过研究影响大气压的因素(例如大气压受质量和气体排放的影响)来进一步挖掘。那么问为什么一个实施者选择32,另一个选择64,与问为什么地球具有特定的表面压力和温度有什么关系呢?前者是由有感知的生命体所做出的选择,而后者在我看来不是,但即使在您看来也是,我认为C ++实施者并没有像上帝那样难以言喻的权利。 - Steve Jessop
显示剩余4条评论
6个回答

61

大多数现代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 5版起使用的libstdc++、Clang的libc++和MSVC的实现)都这样做。其他的实现也可能这样做。


@KonradRudolph 非常小的字符串直接保存在对象中,这可以是栈,也可以是堆,具体取决于字符串本身的分配位置,不是吗? - Manuel Selva
@ManuelSelva 没错。 - Konrad Rudolph
@KonradRudolph 如何强制字符串始终在堆上分配?(为了使字符串对象小于32字节,例如8字节的意图。) - Luke
@LukeFisk-Lennon 你不能这样做。小字符串优化是某些(也就是所有现代)标准库实现的一个实现细节,它没有被语言规定。因此,在C++中无法更改它。你也不能在C++之外更改它(例如通过编译器选项),因为这样的更改将会破坏ABI。话虽如此,GCC4没有执行小字符串优化,因此你原则上可以使用--with-default-libstdcxx-abi=gcc4-compatible配置你的GCC,但那将是一个可怕的想法(=非常旧的实现)。 - Konrad Rudolph
@KonradRudolph 好的,我明白了。感谢您迅速的回复。 - Luke

14

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


通常情况下(在Windows上);-) - Steve Jessop
4
Windows编译器并不是唯一一个进行小字符串优化的编译器。 - Anthony Williams
当然可以,但如果您不愿意给它们命名,那么很难判断这是否是“典型”的行为,或者只是因为它是常见实现的行为(并且可能是其他实现)。 - Steve Jessop
据我所知,Dinkumware和STLPort都有,但gcc的实现没有。 - Dennis Zickefoose
3
顺便提一下,我之所以提到这个是因为“通常”涵盖了从“我相当有信心你不会看到其他任何东西”的范围到“我使用的50%或更多的实现都是这样做的”。我认为这很容易被误解。既不应该认为这种优化是异常的,也不应该认为缺少它是异常的。 - Steve Jessop
请注意,IBM-AIX C++实现包含一个带有32个字符缓冲区的小字符串实现(请参见此处:http://www-01.ibm.com/support/docview.wss?uid=swg21453760)。 - Theo

7
在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;

但是这个想法是一样的。

7
我猜测是:
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;
};

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


啊哦...没有引用计数?折叠功能去哪了? - Erik Aronesty
1
@ErikAronesty 有一个阶段,使用std::string进行引用计数的尝试,但很明显这并不是很高效(多年来有几篇论文对此进行了研究),因此短字符串优化变得流行起来。 - Martin York

3

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

请记住,std::string的实现是由优化了各种用例的人编写的,通常会导致2种内部表示,一种是短字符串(小内部缓冲区),一种是长字符串(堆分配的外部缓冲区)。与容纳这两个内容相关的开销都与每个std::string对象有关。


-2

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

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接