【问题标题】:Resizing a dynamic string causes a memory leak调整动态字符串的大小会导致内存泄漏
【发布时间】:2012-06-15 04:21:13
【问题描述】:

我从一个非常简单的程序开始:

#include <TBString.h>

int main(int argv, char** argc)
{
    tb::String test("");
    test = "Hello World!";

    return 0;
}

tb::String 是我自己的字符串类,旨在处理char 字符串和wchar_t (Unicode) 字符串。它是大量模板化的,tb::Stringtb::StringBase&lt;char&gt; 的 typedef。

整个过程是使用 CRT 调试实用程序编译的,以检查内存泄漏。这是输出:

Detected memory leaks!
Dumping objects ->
c:\users\sam\svn\dependencies\toolbox\headers\tbstring2.inl(38) : {442} normal block at 0x00D78290, 1 bytes long.
 Data: < > 00 
{131} normal block at 0x00C5EFA0, 52 bytes long.
 Data: <                > A0 EF C5 00 A0 EF C5 00 A0 EF C5 00 CD CD CD CD 
Object dump complete.
Detected memory leaks!
Dumping objects ->
c:\users\sam\svn\dependencies\toolbox\headers\tbstring2.inl(38) : {442} normal block at 0x00D78290, 1 bytes long.
 Data: < > 00 
Object dump complete.
The program '[2888] SAM_release.exe: Native' has exited with code 0 (0x0).

所以看起来像一个空的 tb::String(大小为 0)正在导致内存泄漏。用这个程序确认,不会泄露:

#include <TBString.h>

int main(int argv, char** argc)
{
    tb::String test("Hello World!");

    return 0;
}

原程序的调用栈:

  • 使用字符串"" 创建一个StringBase&lt;char&gt;
  • m_Length 设置为 0。
  • m_Maximum 设置为 m_Length + 1 (1)。
  • m_Data 的长度为 m_Maximum (1)。
  • m_Data 被清除并用"" 填充。
  • _AppendSingle 设置为 StringBase&lt;char&gt;::_AppendDynSingle
  • 使用字符串"Hello World!" 调用重载运算符StringBase&lt;char&gt;::operator =
  • _AppendSingle 被调用。
  • m_Length 为 0,m_Maximum 为 1。
  • checklen 设置为 m_Length + src_len + 1 (13)。
  • m_Maximum 乘以 2 直到大于 checklen (16)。
  • StringBase&lt;char&gt;::Resize 函数以新的最大值调用。

调整大小功能:

template <typename C>
TB_INLINE StringBase<C>& StringBase<C>::Resize(int a_Maximum /*= -1*/)
{
    if (!m_Data)
    {
        m_Maximum = (a_Maximum == -1) ? 4 : a_Maximum;
        m_Data = new C[m_Maximum];
        StringHelper::Clear<C>(m_Data, m_Maximum);
    }
    else
    {
        int newmax = (a_Maximum == -1) ? (m_Maximum * 2) : a_Maximum;

        C* temp = new C[newmax];
        StringHelper::Clear<C>(temp, newmax);
        if (m_Length > 0) { StringHelper::Copy(temp, m_Data, m_Length); }
        delete [] m_Data;
        m_Data = temp;

        m_Maximum = newmax;
    }

    return *this;
}

这是我怀疑的问题。现在,我的问题变成了:

如何在 C++ 中重新分配内存而不触发 CRT 调试器中的内存泄漏?

构造函数:

TB_INLINE StringBase<char>::StringBase(const char* a_String)
{
    m_Length = StringHelper::GetLength<char>(a_String);
    m_Maximum = m_Length + 1;
    m_Data = new char[m_Maximum];
    StringHelper::Clear<char>(m_Data, m_Maximum);

    StringHelper::Copy<char, char>(m_Data, a_String, m_Length);

    _AppendSingle = &StringBase<char>::_AppendDynSingle;
    _AppendDouble = &StringBase<char>::_AppendDynDouble;
}

析构函数:

TB_INLINE StringBase<char>::~StringBase()
{
    if (m_Data) { delete [] m_Data; }
}

赋值运算符:

TB_INLINE StringBase<char>& StringBase<char>::operator = (const char *a_String)
{
    Clear();
    return (this->*_AppendSingle)(a_String);
}

附加函数:

template<>
TB_INLINE StringBase<char>& StringBase<char>::_AppendDynSingle(const char* a_String)
{
    if (!a_String) { return *this; }

    int src_len = StringHelper::GetLength<char>(a_String);

    // check size

    if (m_Maximum == -1)
    {
        m_Maximum = src_len + 1;
        m_Data = new char[m_Maximum];
        StringHelper::Clear<char>(m_Data, m_Maximum);
        m_Length = 0;
    }

    int checklen = m_Length + src_len + 1;
    if (checklen > m_Maximum)
    {
        while (checklen > m_Maximum) { m_Maximum *= 2; }
        Resize(m_Maximum);
    }

    // append

    strcat(m_Data, a_String);

    // new length

    m_Length += src_len;

    return *this;
}

请注意:我不想使用std::stringstd::vector,我想修复这个功能。

【问题讨论】:

  • 这个问题似乎主要与tb::String 类有关。这是你现在自己的还是来自某个图书馆的?
  • 什么是tb::String?是什么严重的事情,还是你搞砸了?
  • @user1240436 -1。在数组上使用 delete ptr 会导致堆损坏。
  • @KonradRudolph - 有很多借口:您正在编写自己的智能指针类;您使用的是 C++98 之前的编译器;您正在编写嵌入式(独立式)应用程序;或者你只是想获得一些指针的乐趣!
  • @rodrigo 再次阅读我的评论,我说“在你的代码中”。我专门指的是 OP 的用例。我同意嵌入式环境,但这反对使用delete,而不是反对智能指针。而指针乐趣并不是一个严肃的应用程序。但即使在一般情况下,我也看不出这少数极端情况如何成为“许多借口”。

标签: c++ memory-leaks dynamic-allocation


【解决方案1】:

这会很长。

首先,我决定检查一下自己的理智。 CRT 内存调试器是否正常工作?

int* src_test = new int[10];
for (int i = 0; i < 10; i++) { src_test[i] = i; }
int* dst_test = new int[10];
for (int i = 0; i < 10; i++) { dst_test[i] = src_test[i]; }
delete [] src_test;

这会正确报告 40 个字节的泄漏。此行修复了泄漏:

delete [] dst_test;

好的,还有什么?好吧,也许解构器没有被调用。让我们把它放在一个函数中:

void ScopeTest()
{
    tb::String test("Hello World!");
    test = "Hello World! Again!";
}

它可以工作,但它会泄漏。让我们绝对确保调用了解构函数。

void ScopeTest()
{
    tb::String* test = new tb::String("Hello World!");
    *test = "Hello World! Again!";
    delete test;
}

还在漏水。那么,= 运算符是做什么的?它清除并附加。让我们手动完成:

void ScopeTest()
{
    tb::String* test = new tb::String("Hello World!");
    test->Clear();
    test->Append("Hello World! Again!");
    delete test;
}

结果相同,所以与操作符无关。我想知道如果我删除Clear会发生什么...

void ScopeTest()
{
    tb::String* test = new tb::String("Hello World!");
    test->Append("Hello World! Again!");
    delete test;
}

好吧,它...等等,什么?它不会泄漏?那么Clear 会做什么呢?

template <>
TB_INLINE StringBase<char>& StringBase<char>::Clear()
{
    if (m_Data)
    {
        StringHelper::Clear<char>(m_Data, m_Maximum);
    }

    m_Length = 0;

    return *this;
}

那是……无害的。但是让我们把它注释掉。

template <>
TB_INLINE StringBase<char>& StringBase<char>::Clear()
{
    /*if (m_Data)
    {
        StringHelper::Clear<char>(m_Data, m_Maximum);
    }

    m_Length = 0;*/

    return *this;
}

结果相同,没有泄漏。让我们再次删除对Clear 的调用。

void ScopeTest()
{
    tb::String* test = new tb::String("Hello World!");
    //test->Clear();
    test->Append("Hello World! Again!");
    delete test;
}

再次泄漏字节...

但是等一下,它还在清除tb::String?长度设置为 0 并且数据清零,即使正文已被注释掉。怎么,怎么……

好的,编译器,看你编译这个

/*template <>
TB_INLINE StringBase<char>& StringBase<char>::Clear()
{
    if (m_Data)
    {
        StringHelper::Clear<char>(m_Data, m_Maximum);
    }

    m_Length = 0;

    return *this;
}*/

哈!那会告诉他的!哦等等......它......仍然编译并运行。

我使用的是同一文件的不同版本吗?不,我在这台电脑上只有一个版本的TBString2.hTBString2.inl...

哦。

哦,等一下。

哦,该死的。

最好不要像我想的那样。

Project Toolbox -> $(OutDir)\$(ProjectName)_d.lib

我要杀了这个花了三个小时的人。

Project Game -> Toolbox.lib

哦,等等。那是我。

TL;DR:我链接到旧版本的字符串类,导致各种奇怪的行为,包括内存泄漏。

【讨论】:

    【解决方案2】:

    在执行赋值后,您正在泄漏构造函数中初始化的任何字节。要调试泄漏,请在执行分配时使用调试器单步执行。在m_Data 变量上设置观察点可能很有用,这样调试器就会在它更改值时停止。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-12
      • 1970-01-01
      • 1970-01-01
      • 2017-05-09
      • 2016-03-31
      • 2011-11-09
      • 1970-01-01
      相关资源
      最近更新 更多