提问人:python3.789 提问时间:5/10/2023 更新时间:5/11/2023 访问量:164
std 字符串对象的内部结构
Internal struct of std string object
问:
我试图使用 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 替换为 ?0x00000000
0xffffffff
我也不明白,拜托
答:
根据您的库实现,由于短字符串优化,可能比预期的更复杂。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++。
我查看了库类型的内部结构,以便更多地了解编译器如何执行它的魔力。尤其是容器对象。标准方法是将对象复制到相同大小的对象,然后以十六进制打印数组。这对于探索容器对象被“移动”时发生的情况以及了解不同的库编码人员如何实现容器非常有用。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 个字节!非常高效。
我发现这种方法对于快速理解库容器代码中实际发生的事情非常有用。
评论
std::string