实现operator+=时使用delete[](堆损坏)

发布于 2024-08-30 17:02:18 字数 2516 浏览 3 评论 0 原文

我几个小时以来一直在试图解决这个问题,但我已经无计可施了。如果有人能在我做错的时候告诉我,我一定会很感激。

我编写了一个简单的类来模拟字符串的基本功能。该类的成员包括一个字符指针data(它指向动态创建的字符数组)和一个整数strSize(它保存字符串的长度,没有终止符。

)我正在使用 newdelete,我已经实现了复制构造函数和析构函数。当我尝试实现 operator+= 时,出现了问题。 LHS 对象正确构建新字符串 - 我什至可以使用 cout 打印它 - 但是当我尝试在析构函数中释放数据指针时出现问题:我在指向的内存地址处收到“正常块后检测到堆损坏”通过析构函数尝试释放的 data 数组。

这是我完整的课程和测试程序:

#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;
}

编辑:加速 C++ 问题 12-1。

I've been trying to figure this out for hours now, and I'm at my wit's end. I would surely appreciate it if someone could tell me when I'm doing wrong.

I have written a simple class to emulate basic functionality of strings. The class's members include a character pointer data (which points to a dynamically created char array) and an integer strSize (which holds the length of the string, sans terminator.)

Since I'm using new and delete, I've implemented the copy constructor and destructor. My problem occurs when I try to implement the operator+=. The LHS object builds the new string correctly - I can even print it using cout - but the problem comes when I try to deallocate the data pointer in the destructor: I get a "Heap Corruption Detected after normal block" at the memory address pointed to by the data array the destructor is trying to deallocate.

Here's my complete class and test program:

#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: Accelerated C++ problem 12-1.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

清晨说晚安 2024-09-06 17:02:18

下面的代码块使 p 指向数组旁边。

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

您在复制构造函数中使用的更好(且安全)的解决方案:

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

Following chunk of code makes p pointed beside the array.

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

Better (and safe) solution you have used in copy constructor:

while (*q)
    *p++ = *q++;
*p = '\0';
无悔心 2024-09-06 17:02:18

这里已经有很多很好的答案,但值得将 Valgrind 作为解决此类问题的工具。如果您可以访问 *nix 盒子,Valgrind 工具可以成为真正的救星。

只是为了向您展示,这是我通过它编译和运行程序时得到的结果:

% g++ -g -o test test.cpp
% valgrind ./test
==2293== Memcheck, a memory error detector
==2293== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==2293== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info
==2293== Command: ./test
==2293==
==2293== Invalid write of size 1
==2293==    at 0x8048A9A: Str::operator+=(Str const&) (test.cpp:36)
==2293==    by 0x8048882: main (test.cpp:82)
==2293==  Address 0x42bc0dc is 0 bytes after a block of size 12 alloc'd
==2293==    at 0x4025024: operator 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== 
helloworld!
hello
==2293== 
==2293== HEAP SUMMARY:
==2293==     in use at exit: 0 bytes in 0 blocks
==2293==   total heap usage: 4 allocs, 4 frees, 31 bytes allocated
==2293== 
==2293== All heap blocks were freed -- no leaks are possible
==2293== 
==2293== For counts of detected and suppressed errors, rerun with: -v
==2293== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 17 from 6)
%

您可以看到它精确地指出了此处其他答案指出的行(第 36 行附近)。

There are already a bunch of good answers here, but it's worth plugging Valgrind as a tool for solving exactly this sort of problem. If you have access to a *nix box, the Valgrind tools can be a real lifesaver.

Just to show you, here's what I got when compiling and running your program through it:

% g++ -g -o test test.cpp
% valgrind ./test
==2293== Memcheck, a memory error detector
==2293== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==2293== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info
==2293== Command: ./test
==2293==
==2293== Invalid write of size 1
==2293==    at 0x8048A9A: Str::operator+=(Str const&) (test.cpp:36)
==2293==    by 0x8048882: main (test.cpp:82)
==2293==  Address 0x42bc0dc is 0 bytes after a block of size 12 alloc'd
==2293==    at 0x4025024: operator 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== 
helloworld!
hello
==2293== 
==2293== HEAP SUMMARY:
==2293==     in use at exit: 0 bytes in 0 blocks
==2293==   total heap usage: 4 allocs, 4 frees, 31 bytes allocated
==2293== 
==2293== All heap blocks were freed -- no leaks are possible
==2293== 
==2293== For counts of detected and suppressed errors, rerun with: -v
==2293== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 17 from 6)
%

You can see that it pinpointed the lines that the other answers here pointed out (near line 36).

诠释孤独 2024-09-06 17:02:18
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'
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

Use an idiom similar to what you used in the copy constructor:

while (*i) *p++ = *i++; //in these loops, you only increment if *i was nonzero
while (*q) *p++ = *q++;
*p = '\0'
北城挽邺 2024-09-06 17:02:18

您已经有两个答案指向导致您垃圾堆的特定错误。假设这是家庭作业或其他形式的练习(否则我们都会因为你编写自己的字符串类而对你大喊大叫),这里还有一些需要咀嚼的东西适合您:

  • 如果您觉得需要注释代码,请考虑使其更具表现力
    例如,代替 char* p = str; // 新数据,您可以直接编写char* new_data = str;
    您可以只编写 do_frgl();,而不是 //do frgl 后跟一大块代码。如果函数是内联的,则对生成的代码没有任何影响,但对代码的读者来说却有很大的不同。
  • 包括您的标头在内的每个人都会将命名空间std中的所有内容转储到全局命名空间这根本不是一个好主意。我会像瘟疫一样避免包含您的标头。
  • 您的构造函数应该在初始化列表中初始化其类的成员。
  • 您的 Str::Str(const char*) 构造函数为同一个字符串调用 std::strlen() 两次。
    应用程序代码根据需要尽可能快库代码<另一方面,如果您不知道它以哪个应用程序结束,则应该尽可能快。您正在编写库代码。
  • size() 成员函数是否会返回负值?如果不是,为什么它是有符号整数?
  • 这段代码会发生什么:Str s1, s2; s1=s2?
  • 那么这个呢:Str str("abc"); std::cout<

(如果遇到此问题的任何人都可以想到更多提示,请随意扩展它。)

You already have two answers pointing at the specific error that made you trash the heap. Assuming this is homework or some other form of exercise (otherwise we'd all be yelling at you for writing your own string class), here's a few more things to chew on for you:

  • If you feel the need to annotate the code, consider making it more expressive.
    For example, instead of char* p = str; // new data, you could just write char* new_data = str;.
    Instead of //do frgl, followed by a chunk of code, you could just write do_frgl();. If the function is inlined, it makes no difference for the resulting code, but a lot of difference to readers of the code.
  • Everyone including your header gets everything from namespace std dumped into the global namespace. That's not a good idea at all. I'd avoid including your header like the plague.
  • Your constructors should initialize their class' members in initializer lists.
  • Your Str::Str(const char*) constructor calls std::strlen() twice for the same string.
    Application code should be as fast as needed, library code, on the other hand, where you don't know which application it ends in, should be as fast as possible. You're writing library code.
  • Could the size() member function ever return a negative value? If not, why is it a signed integer?
  • What would happen for this code: Str s1, s2; s1=s2?
  • And what about this: Str str("abc"); std::cout<<str[1];

(If anyone coming across this can think of more hints, feel free to expand this.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文