将字符串存储在c++中的稳定内存中

store strings in stable memory in c++

本文关键字:内存 c++ 字符串 存储      更新时间:2023-10-16

先有一点背景(如果对此感到无聊,请跳到粗体)。

我正试图把两段代码粘在一起。一个是大量使用自定义字符串视图对象的JSON/YML库,另一个是2000年代初的一段代码。

很长一段时间以来,我一直看到奇怪的行为,直到我将其追溯到内存问题,即我在JSON/YML库中构建的字符串视图以const char*作为构造函数,并假设该char数组的内存位置在字符串视图的整个生命期内保持不变。然而,我构建这些视图的一些std::string对象是临时的,所以这不是真的,字符串视图最终指向垃圾。

现在,我认为我很聪明,以std::vector的形式构建了一个缓存,可以容纳所有临时字符串,我会在这些字符串上构建字符串视图,最后只清除缓存——这很容易。

然而,我仍然时不时地看到混乱的字符串,直到我找到原因:有时,当将东西推到超出预先分配大小的向量时,向量会被移动到不同的内存位置,从而使所有字符串视图无效。目前,我已经决定预先分配一个足够大的缓存大小,以避免矢量的任何可能的移动,但我可以看到,这会在未来的大型运行中造成严重且不可传输的问题。所以我的问题是:

如何构造一个std::vector<std::string>或任何其他字符串容器,以避免在内存中一起移动,或者在发生这种情况时至少抛出一条错误消息

当然,如果你觉得我在根本上以错误的方式处理整个问题,也请告诉我应该如何处理这个问题。

如果您感兴趣,有问题的两段代码是RapidYAML和CERN统计库ROOT。

我对类似问题的回答是:当矢量改变功能时,有什么方法可以更新指针/参考值?

如果将向量中的对象存储为std::unique_ptr或std::shared_ptr,则可以使用std::unique_ptr::get()获取指向底层对象的观察指针(如果取消引用智能指针,则为引用)。这样,即使智能指针的内存位置在调整大小时发生变化,观察指针也指向相同的对象,从而指向相同的内存位置。

[…]有时,当将东西推送到超出预先分配大小的向量时,向量会被移动到不同的内存位置,从而使所有字符串视图无效。

原因是std::vector需要将其数据连续存储在内存中。因此,如果在添加元素时超过了向量的最大容量,它将在内存中分配一个新空间(这次足够大),并将所有数据移动到这里。

您受到的影响称为迭代器无效

如何构建一个std::vector或任何其他字符串容器,以避免在内存中一起移动,或者在发生这种情况时至少抛出一条错误消息?

您至少有3个简单的解决方案:

  1. 如果您的缓存大小应该是固定的,并且在编译时已知,我建议您改用std::array
  2. 如果您的缓存大小应该是固定的,但在编译时不一定是已知的,我建议您将std::vector所需的容量reserve(),这样您就可以保证它足够大,不需要重新分配
  3. 如果您的缓存大小可能会更改,我建议您改用std::list。它被实现为一个(通常是双重)链表。它将保证元素不会在内存中重新定位
    但由于它们不是连续存储在内存中的,您将失去直接访问任何元素的能力(即,您需要遍历列表才能找到元素)

当然,可能还有其他解决方案(我并不认为这个答案是详尽无遗的),但这些解决方案将允许您几乎不更改代码(仅更改容器),并保护您的字符串视图无效。

也许可以使用std::list。它的访问方法较慢(至少在迭代时),但内存位置是恒定的。两者的原因都是它不使用连续内存。

或者,创建一个包装器,该包装器包装指向使用"new"创建的字符串的指针。该地址也将保持不变。编辑:不知怎么的,我忽略了我刚才描述的几乎是一个智能指针减去自动删除;)

遗憾的是,在确保内容至少在经典操作系统上保持不变的同时,不可能增长向量。

有一个函数realloc试图保持不变,但正如你在文档中所读到的,这并不能保证,只有操作系统会决定。

为了解决你的问题,你需要一个池的概念,这里是一个字符串池,用来处理字符串的使用寿命。

您可能只需要一个简单的字符串std::list,但它会导致糟糕的数据混叠和大量的独立分配,这对您的性能不利。这些也将是智能指针的问题。

因此,如果你关心性能,在我看来,你如何在你的案例中实现它可能与你目前的实现不远。因为您无法调整向量的大小,所以您应该更喜欢在编译时决定的固定大小的std::array。然后,只要你需要,你就可以创建一个新的来扩展你的内存容量。这通常可以通过CCD_ 12来容易地实现。

我不知道它是否适用于这里,但如果你的应用程序在执行过程中可以创建任意数量的字符串,你必须小心,因为这可能会导致不断增长的内存池,最终可能会导致内存问题。为了解决这个问题,您可以确保不再使用的字符串可以被重用或释放。遗憾的是,我在这里帮不了你太多,因为这些规则将取决于你的申请。