3

私は非常に単純なプログラムから始めます:

#include <TBString.h>

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

    return 0;
}

tb::Stringchar文字列とwchar_t(Unicode)文字列の両方を処理するように設計された私自身の文字列クラスです。これは高度にテンプレート化されてtb::Stringおり、のtypedefですtb::StringBase<char>

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<char>文字列を使用してを作成します""
  • m_Length0に設定されます。
  • m_Maximumm_Length + 1(1)に設定されます。
  • m_Datam_Maximum(1)の長さで作成されます。
  • m_Dataクリアされ、で埋められ""ます。
  • _AppendSingleに設定されStringBase<char>::_AppendDynSingleます。
  • オーバーロードされた演算子StringBase<char>::operator =は文字列で呼び出されます"Hello World!"
  • _AppendSingleと呼ばれます。
  • m_Lengthは0、m_Maximumは1です。
  • checklenm_Length + src_len + 1(13)に設定されます。
  • m_Maximumchecklen(16)より大きくなるまで2を掛けます。
  • StringBase<char>::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;
}

これが問題だと私は思う。今、私の質問は次のようになります:

CRTデバッガーでメモリリークをトリガーせずに、C ++でメモリを再割り当てするにはどうすればよいですか?

コンストラクタ:

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::stringまたはstd::vector、この機能を修正したい。

4

2 に答える 2

0

これは長いものになるでしょう。

まず、正気を確認することにしました。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.hいいえ、このコンピュータには1つのバージョンしかありませんTBString2.inl...

おー。

ちょっと待って。

なんてこった。

これは私が思っていることではない方がいいです。

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

これに3時間を費やした人を殺害するつもりです。

Project Game -> Toolbox.lib

えっ、ちょっと待って。それは私でした。

TL; DR:文字列クラスの古いビルドにリンクしたため、メモリリークなど、あらゆる種類の奇妙な動作が発生しました。

于 2012-06-12T13:55:29.643 に答える
0

割り当てを実行した後、コンストラクターで初期化されたバイトがリークしています。リークをデバッグするには、割り当てを実行するときにデバッガーをステップ実行します。m_Data値が変更されるたびにデバッガーが停止するように、変数にウォッチポイントを設定すると便利な場合があります。

于 2012-06-12T11:35:59.447 に答える