std 字符串对象的内部结构

Internal struct of std string object

提问人:python3.789 提问时间:5/10/2023 更新时间:5/11/2023 访问量:164

问:

我试图使用 GDB 理解 std::String 的内部结构,我想看看我是否也理解这一点。

我有包含字符串 (32 A) 的 std::string 对象。AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

当我研究 GDB 时,我看到 页眉:0x00000020 0x00000020 0x00000000

数据:0x41414141 0x41414141 0x41414141 0x41414141 0x41414141 0x41414141 0x41414141 0x41414141

当该对象释放时,我看到数据相同,但是,标头:std::string::~string

0x00000000 0x00000020 0xffffffff

是吗? 0x20是字符串的大小(为什么我看到它两次? ) 当 std::string 对象被 release 替换为 ?0x000000000xffffffff

我也不明白,拜托

C++ 字符串 gdb std

评论

0赞 Thomas Matthews 5/10/2023
你的理解可能是正确的。删除对象后,没有定义的操作或内存状态;内存处于未定义状态。
0赞 user17732522 5/10/2023
您正在关注哪个标准库实现?当然,每个实现都可以随心所欲地实现它,只要满足标准的规范。C++没有像Python那样的参考实现,通常也没有标准库类的内存布局规范。std::string
0赞 alagner 5/10/2023
您可能想观看此CPPCON演示文稿:youtube.com/watch?v=kPR8h4-qZdk

答:

3赞 RandomBits 5/10/2023 #1

根据您的库实现,由于短字符串优化,可能比预期的更复杂。std::string

从这个答案中解释,一个简单的实现可能会存储大小、容量和指向数据的指针。

简单

class string {
...
private:
    std::unique_ptr<char[]> m_data;
    size_type m_size;
    size_type m_capacity;

而性能更高的实现可能会将短字符串直接存储为大小和缓冲区,而将较长的字符串存储为大小、容量和指向数据的指针。

优化

class string {
...
private:
    size_type m_size;
    union {
        class {
            std::unique_ptr<char[]> m_data;
            size_type m_capacity;
        } m_large;
        std::array<char, sizeof(m_large)> m_small;
    };
};

在调试器中解开所有这些可能很困难。

您也可以根据您的库直接读取代码,例如 libc++

1赞 doug 5/11/2023 #2

我查看了库类型的内部结构,以便更多地了解编译器如何执行它的魔力。尤其是容器对象。标准方法是将对象复制到相同大小的对象,然后以十六进制打印数组。这对于探索容器对象被“移动”时发生的情况以及了解不同的库编码人员如何实现容器非常有用。std::array

以下是适应 的基本代码。检查对象如何在空字符串、具有最大值的字符串、SSO、内容和要求将字符串存储在堆中的字符串之间切换。std::string

SSO 优化字符串要求指向字符串中任何字符的指针位于字符串对象的开头和结尾之间。

#include <string>
#include <array>
#include <iostream>
#include <iomanip>
#include <cstdint>

void print_string_object(std::string& s)
{
    // Check that the size of a string object is a multiple of a pointer size
    static_assert(sizeof(uintptr_t) * (sizeof(s) / sizeof(uintptr_t)) == sizeof(s));

    // Create an array of uintptr_t that is the same size as a string
    using s_obj = std::array<uintptr_t, sizeof(s) / sizeof(uintptr_t)>;
    s_obj s_ptrs = *reinterpret_cast<s_obj*>(static_cast<void*>(&s));

    // Print details of string object in hex
    std::cout << "Address of Object\n  " << std::setfill('0') << std::setw(2*sizeof(uintptr_t)) << std::hex << &s << "\nObject\n";
    for (auto x : s_ptrs)
        std::cout << "  " << std::setfill('0') <<  std::setw(2 * sizeof(uintptr_t)) << std::hex << x << '\n';
}

int max_SSO(std::string &s)
{
    // return the maximum string stored in a string object (SSO)
    // and set s with bytes 0 1 2 3 ... until SSO is maxed out
    std::string s0;
    uintptr_t base = reinterpret_cast<uintptr_t>(&s0);
    uintptr_t top = reinterpret_cast<uintptr_t>(&s0) + sizeof(s0);
    for (int i = 0;; i++)
    {
        s0 += static_cast<char>(i);
        if (reinterpret_cast<uintptr_t>(&s0[0]) < base || reinterpret_cast<uintptr_t>(&s0[0]) >= top)
            return i;
        s += static_cast<char>(i);
    }
}

int main()
{
    std::string s;
    std::cout << "Capacity of empty string=" << s.capacity() << '\n';
    std::cout << "Empty string\n";
    print_string_object(s); // print details of null string 
    std::cout << "\nFull SSO string length=" << std::dec <<  max_SSO(s) << "\n";
    print_string_object(s); // print details of max SSO string 
    s += "0";
    std::cout << "\nDynamic memory string\n";
    print_string_object(s); // print details of dynamic allocated string 
}

这是 clang 和 gcc 的编译器资源管理器的链接

MSVC 输出 x64 为:

    Capacity of empty string=15
Empty string
Address of Object
  000000AF535AF840
Object
  0000000000000000
  0000000000000000
  0000000000000000
  000000000000000f

Full SSO string length=15
Address of Object
  000000AF535AF840
Object
  0706050403020100
  000e0d0c0b0a0908
  000000000000000f
  000000000000000f

Dynamic memory string
Address of Object
  000000AF535AF840
Object
  00000235a757e8a0
  000e0d0c0b0a0908
  0000000000000010
  000000000000001f

对于 MSVC,前 16 个字节用于存储 SSO 字符。这允许字符串长度为 15,并具有所需的终止 null char。当较长的字符串需要动态内存时,前 8 个字节是指向堆中存储的字符的指针。最后 2 个条目是当前字符串大小和需要内存分配之前所需的最大字符串大小。GCC 和 CLANG 的布局略有不同。特别是 CLANG 允许 SSO 字符串大小最多 22 个字符,并且它的对象大小少了 8 个字节!非常高效。

我发现这种方法对于快速理解库容器代码中实际发生的事情非常有用。