2

私はこれを何時間も理解しようとしてきましたが、私は機知に富んでいます。私が間違っているときに誰かが私に教えてくれれば、きっとありがたいです。

文字列の基本機能をエミュレートする単純なクラスを作成しました。クラスのメンバーには、文字ポインターデータ(動的に作成された char 配列を指す) と整数のstrSize (文字列の長さを保持し、ターミネーターを除く) が含まれます。

newdeleteを使用しているので、コピー コンストラクタとデストラクタを実装しました。operator+=を実装しようとすると、問題が発生します。LHS オブジェクトは新しい文字列を正しく構築します - cout を使用して出力することもできます - しかし、デストラクタでデータ ポインタの割り当てを解除しようとすると問題が発生します。ポイントされたメモリ アドレスで「通常のブロック後にヒープ破損が検出されました」というメッセージが表示されます。デストラクタが割り当てを解除しようとしているデータ配列によって。

これが私の完全なクラスとテストプログラムです:

#include <iostream>

using namespace std;

// Class to emulate string
class Str {
public:

    // Default constructor
    Str(): data(0), strSize(0) { }

    // Constructor from string literal
    Str(const char* cp) {
        data = new char[strlen(cp) + 1];
        char *p = data;
        const char* q = cp;
        while (*q)
            *p++ = *q++;
        *p = '\0';
        strSize = strlen(cp);
    }

    Str& operator+=(const Str& rhs) {
        // create new dynamic memory to hold concatenated string
        char* str = new char[strSize + rhs.strSize + 1];

        char* p = str;                  // new data
        char* i = data;                 // old data
        const char* q = rhs.data;       // data to append

        // append old string to new string in new dynamic memory
        while (*p++ = *i++) ;
        p--;
        while (*p++ = *q++) ;
        *p = '\0';

        // assign new values to data and strSize
        delete[] data;
        data = str;
        strSize += rhs.strSize;
        return *this;
    }


    // Copy constructor
    Str(const Str& s)
    {
        data = new char[s.strSize + 1];
        char *p = data;
        char *q = s.data;
        while (*q)
            *p++ = *q++;
        *p = '\0';
        strSize = s.strSize;
    }

    // destructor
    ~Str() { delete[] data;  }

    const char& operator[](int i) const { return data[i]; }
    int size() const { return strSize; }

private:
    char *data;
    int strSize;
};

ostream& operator<<(ostream& os, const Str& s)
{
    for (int i = 0; i != s.size(); ++i)
        os << s[i];
    return os;
}


// Test constructor, copy constructor, and += operator
int main()
{
    Str s = "hello";        // destructor  for s works ok
    Str x = s;              // destructor for x works ok
    s += "world!";          // destructor for s gives error
    cout << s << endl;
    cout << x << endl;
    return 0;
}

EDIT : 高速化された C++ 問題 12-1。

4

4 に答える 4

4

次のコードのチャンクは、 p を配列の横に指すようにします。

while (*p++ = *q++) ;
*p = '\0';

コピーコンストラクターで使用したより良い(そして安全な)ソリューション:

while (*q)
    *p++ = *q++;
*p = '\0';
于 2010-05-02T18:03:53.397 に答える
3

ここにはすでにたくさんの良い答えがありますが、この種の問題を正確に解決するためのツールとしてValgrindを接続する価値があります。* nixボックスにアクセスできる場合、Valgrindツールは実際の命の恩人になる可能性があります。

念のために言っておきますが、プログラムをコンパイルして実行したときに得られたものは次のとおりです。

%g ++ -g -o test test.cpp
%valgrind ./test
== 2293 == Memcheck、メモリエラー検出器
== 2293 == Copyright(C)2002-2009、and GNU GPL'd、by Julian Sewardetal。
== 2293==Valgrindの使用-3.5.0-DebianとLibVEX; 著作権情報については、-hを指定して再実行してください
== 2293 ==コマンド:./ test
== 2293 ==
==2293==サイズ1の無効な書き込み
== 2293 == 0x8048A9A:Str :: operator + =(Str const&)(test.cpp:36)
== 2293 == by 0x8048882:main(test.cpp:82)
== 2293 ==アドレス0x42bc0dcは、サイズ12のブロックが割り当てられた後の0バイトです
== 2293 == at 0x4025024:演算子new [](unsigned int)(vg_replace_malloc.c:258)
== 2293 == by 0x8048A35:Str :: operator + =(Str const&)(test.cpp:26)
== 2293 == by 0x8048882:main(test.cpp:82)
== 2293 ==
こんにちは世界!
こんにちは
== 2293 ==
== 2293 ==ヒープの概要:
== 2293 ==終了時に使用中:0ブロックで0バイト
== 2293 ==合計ヒープ使用量:4つの割り当て、4つの解放、31バイトの割り当て
== 2293 ==
==2293==すべてのヒープブロックが解放されました-リークは発生しません
== 2293 ==
== 2293 ==検出および抑制されたエラーのカウントについては、-vを指定して再実行します。
== 2293 ==エラーの概要:1つのコンテキストからの1つのエラー(抑制:6からの17)
%

ここで他の回答が指摘した行(36行目付近)を正確に示していることがわかります。

于 2010-05-03T06:50:21.637 に答える
2
while (*p++ = *i++) ; // the last iteration is when i is one past the end
// i is two past the end here -- you checked for a 0, found it, then incremented past it
p--; //here you corrected for this
while (*p++ = *q++) ;// the last iteration is when p and q are one past the end
// p and q are two past the end here
// but you didn't insert a correction here
*p = '\0';  // this write is in unallocated memory

コピー コンストラクターで使用したのと同様のイディオムを使用します。

while (*i) *p++ = *i++; //in these loops, you only increment if *i was nonzero
while (*q) *p++ = *q++;
*p = '\0'
于 2010-05-02T18:06:29.143 に答える
1

ヒープを破棄した特定のエラーを指す 2 つの回答が既にあります。これが宿題またはその他の形式の演習であると仮定すると(そうしないと、独自の文字列クラスを作成したことに対して皆が怒鳴ることになるでしょう)、ここでさらにいくつかのことを考えてみましょう。

  • コードに注釈を付ける必要がある場合は、表現力を高めることを検討してください
    たとえば、 の代わりにchar* p = str; // new data、 と書くことができますchar* new_data = str;
    の代わりに//do frgl、コードのチャンクが続く代わりに、単に と書くことができますdo_frgl();。関数がインライン化されている場合、結果のコードには違いはありませんが、コードの読者には大きな違いがあります。
  • ヘッダーを含むすべての人が、グローバル名前空間にダンプされた名前空間からすべてをstd取得します。それはまったく良い考えではありません。ペストのようにヘッダーを含めることは避けたいと思います。
  • コンストラクターは、クラスのメンバーを初期化リストで初期化する必要があります。
  • コンストラクターが同じ文字列を 2 回Str::Str(const char*)呼び出しています。アプリケーション コードは必要に応じてできるだけ高速にする必要がありますが、ライブラリ コードは、どのアプリケーションで終了するかわからない場合は、できるだけ高速にする必要があります。ライブラリコードを書いています。std::strlen()
  • メンバー関数がsize()の値を返すことはありますか? そうでない場合、なぜ符号付き整数なのですか?
  • このコードではどうなるでしょうか: Str s1, s2; s1=s2?
  • そして、これはどうですか:Str str("abc"); std::cout<<str[1];

(これに出くわした人がさらにヒントを思い付くことができる場合は、自由にこれを拡張してください。)

于 2010-05-02T18:44:42.347 に答える