当前位置: 首页 > 知识库问答 >
问题:

使用新位置移动std::字符串后内存泄漏

郑宜民
2023-03-14

我有一个包含字符串的结构,该结构用于向量。当向量增长时,所有元素都移动到新的分配。不幸的是,这一举动还导致std::string内存泄漏。

以下是一些最小可重复的情况。第一个示例将说明内存泄漏发生的位置,但可以对此做出判断。第二个例子将涵盖困扰我的内存泄漏。第三个将更进一步。最后,我将展示实际用例来演示我正在做什么,以及为什么我会问这个问题。

int main(void)
{
  char* allocation = new char[sizeof(std::string)];
  std::string start("start");
  std::string* move = (std::string*)allocation;
  new (move) std::string(std::move(start));
  delete[] allocation;
}

不出所料,这会导致内存泄漏。构建开始字符串,并为数据分配内存。然后将该字符串移动到移动中,并在程序的其余部分保持不变。由于它已被移动,将不会调用移动的析构函数,这将导致如下所示的内存泄漏。我创建字符数组的原因是为了避免对移动字符串的默认构造函数和析构函数调用。

Detected memory leaks!
Dumping objects ->
{207} normal block at 0x00000278D16848C0, 16 bytes long.
 Data: <  g x           > D0 D7 67 D1 78 02 00 00 00 00 00 00 00 00 00 00
Object dump complete.

这里需要注意的一件有趣的事情是data

现在,我们将在前面的示例中添加一行,以尝试消除内存泄漏。

int main(void)
{
  char* allocation = new char[sizeof(std::string)];
  std::string start("start");
  std::string* move = (std::string*)allocation;
  new (move) std::string(std::move(start));
  start = std::move(*move); // The new line.
  delete[] allocation;
}

现在,该字符串不再保留在移动直到程序结束,而是被移回start,并且由于start的析构函数应该被调用,所以字符串的分配应该被释放。然而,当我运行它时,我仍然会遇到类似的内存泄漏。

Detected memory leaks!
Dumping objects ->
{207} normal block at 0x000001AE813FECC0, 16 bytes long.
 Data: <  @             > 80 15 40 81 AE 01 00 00 00 00 00 00 00 00 00 00
Object dump complete.

我还尝试了使用单个字符串分配而不是分配字符数组的测试,我仍然遇到了与下面代码类似的内存泄漏。

int main(void)
{
  std::string start("start");
  std::string* move = new std::string;
  new (move) std::string(std::move(start));
  start = std::move(*move);
  delete move;
}

我这样做的原因是因为我正在编写我自己版本的向量。当我的向量增长时,并且向量中包含的类型包含一个字符串,我在上面突出显示的内存泄漏就会发生。这就是我的增长函数的样子。Util::移动是我自己版本的std::移动。出于好奇、学习和娱乐的目的,我做了很多这样的事情。

template<typename T>
void Vector<T>::Grow(int newCapacity)
{
  LogAbortIf(
    newCapacity <= mCapacity,
    "The new capacity must be greater than the current capacity.");

  T* oldData = mData;
  mData = CreateAllocation(newCapacity);
  for (int i = 0; i < mSize; ++i)
  {
    new (mData + i) T(Util::Move(oldData[i]));
  }
  mCapacity = newCapacity;
  DeleteAllocation(oldData);
}

// Using this struct in the vector would cause a leak.
struct Example
{
  std::string mString;
};

为什么会发生内存泄漏?我在这里遗漏了关于std::string的一些内容,或者我对新位置的使用是罪魁祸首吗?

附加说明:标准库字符串的新位置内存泄漏-这个问题只涉及我的第一个示例。它没有解释或涵盖最后两个。


共有2个答案

严元白
2023-03-14

从对象移动并不意味着不需要调用析构函数。任何构造的对象都需要被析构:

{
    std::string s;
    std::move(s);
} // s’ destructor is run here!

您在代码中通过放置新(位于地址mobile)显式构造的对象也是如此。问题是,由于它驻留在动态分配的内存中,它的析构函数不会自动运行,当您删除内存底层char数组时,您告诉C不要考虑该存储位置的字节被std::字符串对象占用。

简而言之,解决方案是在释放存储之前手动运行std::字符串析构函数:

move->std::string::~string();
delete[] allocation;

每一次安置都这样做-新的。

沈高峻
2023-03-14

因为它已被移动,所以不会调用move的析构函数

不,这是错误的。不会调用名为*mobile的对象的析构函数,因为mobile是一个原始指针。

如果它是自动调用的,那么它会在您删除基础分配后发生,因此无论如何它都会导致UB。这种代码完全是荒谬的。

我创建字符数组的原因是为了避免对移动字符串的构造函数和析构函数调用

您显式地将放置称为new,因此这也是无稽之谈。故意调用移动构造函数如何避免构造函数?

唯一可以避免的是析构函数,这是一个bug,而不是一个优化。

... 这意味着应该调用start的析构函数,并且应该释放字符串的分配。。。

start的析构函数将被调用,因为它是自动本地的。它也在第一个示例中被调用。但是,您假设从*mobile移动意味着可以不调用它的析构函数。这不是真的,它仍然是一个错误(即使它没有泄漏,它仍然是一个错误)。

您的最终测试动态分配(和默认构造)一个字符串,然后将new的另一个字符串放置在其存储上,而不首先破坏它。这也是胡说八道。

您的问题依次是:

>

  • 你没有展示泄露的真实代码,所以我们无法真正解释那个泄露。你所有的例子都根本是错误的,所以我们只能推测你的真实代码可能以同样的方式是错误的。

    你试图优化一些可能不会从中受益的东西。许多库实现将对五个字符的字符串使用短字符串优化,看看您的字符串是否属于其中之一。

    就此而言,在开始优化代码之前,不要忘记对其进行分析

    你试图用一种你不理解的技术来优化一些可能不需要它的东西。

    您需要了解对象生存期规则,以及何时调用构造函数和析构函数,然后再开始尝试使用新的放置。手动控制对象和存储生命周期是相当先进的,除非您首先了解默认行为,否则无法正确地进行控制。

    通常的学习实验是编写一个玩具类,其中实现了所有构造函数、赋值运算符和析构函数,并全部打印出一些内容。然后,通过在这些示例中用类替换std::string,您可以很容易地看到发生了什么。

  •  类似资料:
    • 我正在使用此处列出的书籍学习C中的放置新。现在,为了查看一些示例,我在SO帖子中发现了以下片段,声称它(给定示例)具有未定义的行为: 例如,这有UB: 如您所见,用户声称上面的片段有UB。但是我认为它有内存泄漏,而不是UB。有人能告诉我上面的片段是有UB还是内存泄漏,或者两者兼而有之,我的理解(它有内存但没有UB)是否正确。

    • 问题内容: 我正在尝试从屏幕的底部到中间在图像视图上进行翻译动画。动画完成后,我希望图像视图停留在那里。我不要setFillAfter(true),因为我想更新imageview的实际位置。 目前,我通过具有2个图像视图(在动画开始时一个,在结束时一个)来做到这一点,并且我使用setVisibility来实现此目的。这是做事的正确方法吗?这是我使用的代码: 问题答案: 然后,必须为要设置动画的Vi

    • 这种思考方式(静态+动态对于每个字符串是它占用的全部内存)正确吗? 也就是说,如果我有一个Std::string向量,并且我也想计算该向量的所有内存,我需要做同样的事情:我把向量的初始/静态大小加到动态部分,这意味着一个字符串占用的总内存,就像上面对向量中的每个字符串所做的那样? 总而言之,这是我的“缓存”占用的正确内存量吗? 对于每个-我需要添加,另外对于每个-添加??

    • 我在继续我的游戏超过8次后,我得到了OutOfMemory错误,因为堆逐渐填充。在使用MAT分析我的游戏堆时,我知道以下2个原因: 关键词Android.Graphics.Bitmap Byte[] 关键词java.lang.Object[]Android.content.res.resources 请提出解决方案

    • 我在Java中有一个包含字符串的对象。我很好奇字符串的内存使用是如何工作的。我试图优化我的程序的内存使用,应用程序将有大约10000个这样的对象。对于像“Hello World”这样的字符串,内存使用量是多少?

    • 考虑以下初始化: 在g 5.2.0中,编译器对falseString发出警告,而对trueString发出错误。 使用clang 3.6-std=c 11时,编译器会对假字符串和真字符串抛出错误。 Q1)为什么的行为不同,即使两个初始化值是相同的类型()? Q2)哪个编译器是正确的,为什么?标准怎么说? 编辑: 错误:从“bool”到“std::string”(又名“basic\u string”