【问题标题】:What is stored in C++ STL::MAP internal node?C++ STL::MAP 内部节点中存储了什么?
【发布时间】:2017-07-14 08:46:18
【问题描述】:

我已经用 (int, string) 声明了一个地图对象。字符串大小为 128 字节。但是,新的 Map 节点大小保持不变,为 48 字节。我已经通过自定义分配检查了这一点。

std::map<int, std::string, std::less<int>, my_allocator< std::pair<const int, std::string> > >  custom_map;

gen_random(random_string, 103); //generates a random string of size 103 and stored in random_string
custom_map.emplace(i, std::string(random_string)); //allocates 48 bytes for map
                                          /* the string is allocated separately */

我的问题是 - 地图节点包含什么? (我假设基于上述代码的行为,红黑树的一些元数据、键和指向值的指针都保存在映射节点中。)


动机:

我有一个管理持久内存的内核模块。它通过将持久内存映射到应用程序地址空间来使应用程序可以访问它们。它还支持原子 msync。

我正在尝试使用 C++ STL 映射开发一个简单的持久键值存储。因此,我尝试创建一个自定义分配器来从持久内存中分配 stl::map 对象。我有一个从持久内存设备映射的内存池,由自定义分配器使用。所以我需要确保与 MAP 相关的所有内容(键、值、内部节点)都是从该池中分配的。

当我看到 Map 对象/节点大小小于 (int, string) 对大小时,我感到困惑,因为我假设所有内容(键+值)都将包含在使用自定义分配器分配的映射节点内.然而,事实并非如此。所以我需要了解 MAP 节点设置,以保证与 Map 对象相关的所有内容(不多或少)都是从持久内存池中分配的。

我希望它能清除动机。任何建议都受到高度赞赏。

【问题讨论】:

  • 一个字符串不可能有大小,正如 sizeof 所报告的那样。 128 字节 - 更可能是 12 字节。
  • 我猜这主要取决于实现。顺便说一句,std::string 是固定大小的,它包含一个指向动态分配的 char 数组的指针。
  • 它是特定于实现的,你不应该关心。如果您使用GCC 编译,您可以查看源代码(在&lt;map&gt; 及其包含的内部头文件中);还有Clang,它有一个不同的 C++ 标准库。
  • 你会对cout &lt;&lt; sizeof(std::string(random_string)) &lt;&lt; endl;的输出感到惊讶,但如果你考虑一下就明白了。
  • 谢谢! @PeterLenkefi。现在更有意义了。 std::string s = "Test"; 分配 29 个字节。所以,我猜字符串对象是 24 个字节,char 数组是 5 个字节。

标签: c++ dictionary stl dynamic-allocation


【解决方案1】:

第一件事:std::string 具有固定大小(取决于实现)。它通常包含一个指向动态分配的内存块的指针(您可以使用 c_str() 方法访问)。

其次,map的内部没有指定,它也是依赖于实现的。你可以看看this最小实现。

C++ 不支持动态大小的结构(如果我错了,请纠正我!)所以每个动态容器在某处都有一个底层指针。

【讨论】:

  • 谢谢!链接和关于固定大小字符串的事情有帮助。
【解决方案2】:

它是特定于实现的。您可以谷歌并找到例如thisthat

如果您真的在乎,请查看源代码(例如 GCC 及其 libstdc++Clang 及其 libc++)。他们的标准头文件(例如&lt;map&gt;)包括一些特定于实现的内部头文件。您可以将-H(打印实际包含的文件)传递给g++(或clang++)以了解实际使用的内部标头。

同时查看&lt;string&gt;。它经常进行小字符串优化。见this(和that)。

在内部,当您启用 compiler optimizations 时,可能会发生奇怪的事情(例如,生成向量机指令 à la SSEAVX 以加快某些字符串操作)。并且内部标头可能对他们有用。

因此,如果您真的很在意(假设您使用的是 free software C++ 实现,例如 GCCClang),请深入研究实现源代码。或者查看生成的汇编代码(来自您的特定源代码),例如使用g++ -O -fverbose-asm -S 生成。或者查看GIMPLE 内部表示(使用g++ -fdump-tree-gimple -O etc...)。

(内部标头可能不包含可读性很强的代码,并且可能使用编译器魔法或技巧,但编译器和标准 C++ 库的实现者——实际上,它们是同一个团队——为了提高效率正在做一些棘手的事情)

最近 C++ 的兴趣之一是利用 abstractions 提供的优势,并且可能由其 standard library 提供。那么你为什么要关心实现细节呢?你不能相信实现吗?

(如果您关心实现细节,请务必要求至少使用-O-O2 进行优化,因为编译器做了很多)


补充,动机之后

注意用 C++ 编写 Linux 内核模块是 not advised,特别是因为 内核 ABIcalling conventions 与那些不兼容海湾合作委员会。有很多不合理的地方,例如异常(并且生成的代码包含与它们相关的内容)和其他内容。请务必查看生成的汇编代码。

(如果你敢在 C++ 中编写内核代码,请注意所有细节,包括调用约定和内核 ABI。这些细节更可怕,也更复杂只是std::map的实现

如果您仅在特定内核原语(C 语言)之上编写用户级库,则需要关注ASLR 等细节。也许研究现有的application checkpointing 框架(或persistence 实现)可能会很有启发性。垃圾收集与您的持久密钥存储有很多共同的概念和术语,因此阅读GC handbook 应该会有所帮助。

那么我目前的建议是避免用 C++ 编写你的东西;您当然应该关心实现细节,而 C++ 在将它们隐藏给您方面做得很好。所以我觉得你的方法是有缺陷的,而且肯定很脆弱!我建议在 C 中显式编码一些 red-black trees,而不使用任何 C++;那么你就会知道每一个实现细节

【讨论】:

  • 我正在使用 GCC。不过,我主要用 C 编写代码。我正在尝试从自定义内存池中分配所有堆内存以用于研究目的。然而,C++ 内存分配并不是我现在看到的那么简单。据我现在从您的 cmets 了解到,map 的分配器仅从堆中分配节点,很可能它包含键(或指向键的指针)和指向值的指针。我必须检查标题以确保。非常感谢您探索标题的建议。谢谢!
  • 我不熟悉这些标头(即使我知道一点 GCC 的内部结构)。如果你需要探索它们,你应该预算几个星期的工作。你确定值得付出这样的努力吗? C++ 标准容器的优势在于抽象这些实现细节。
  • 嗯,这不是一个好消息,因为我的最后期限快到了。我想我以后必须探索它们。感谢您的提醒!我从您的 cmets 那里得到了一些想法,并回答了下一步该做什么。当我可以深入了解帖子时,我会更新帖子。
  • @mchow:请编辑你的问题来解释你为什么这么关心?你的具体问题是什么?
  • @mchow:我仍然希望你能解释你为什么要问这个问题,通过一些句子来改善你的问题,给出你的动机。我觉得你欠我们的。我们确实花了一些时间给出答案和链接。所以你真的应该解释你为什么要问(简单的好奇心可能是一种动机,但我希望它不是唯一的)。
猜你喜欢
  • 1970-01-01
  • 2012-01-27
  • 2013-05-12
  • 2013-05-21
  • 1970-01-01
  • 2014-01-08
  • 2020-03-11
  • 2022-01-04
  • 1970-01-01
相关资源
最近更新 更多