【问题标题】:Using delete[] (Heap corruption) when implementing operator+=在实现 operator+= 时使用 delete[](堆损坏)
【发布时间】:2011-02-14 19:29:36
【问题描述】:

我已经尝试解决这个问题好几个小时了,但我已经束手无策了。如果有人能在我做错时告诉我,我一定会很感激。

我编写了一个简单的类来模拟字符串的基本功能。该类的成员包括一个字符指针 data(它指向一个动态创建的 char 数组)和一个整数 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。

【问题讨论】:

  • 这需要一些明显的问题 - 或 homework 标签。

标签: c++ operator-overloading operators new-operator delete-operator


【解决方案1】:

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

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

(如果有人遇到这个可以想到更多提示,请随时扩展。)

【讨论】:

  • 感谢您的提示。这本身不是家庭作业。我是一名在职专业人士,在自己的时间学习 C++。这来自“加速 C++”问题 12-1。
  • 啊,Accelerated C++,一个很好的选择,虽然它有一个陡峭的学习曲线。请务必查看您的下一本 C++ 书籍的权威 C++ 书籍清单:stackoverflow.com/questions/388242
【解决方案2】:

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

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

% g++ -g -o 测试 test.cpp % valgrind ./测试 ==2293== Memcheck,内存错误检测器 ==2293== 版权所有 (C) 2002-2009 和 GNU GPL,由 Julian Seward 等人提供。 ==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: 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== 你好世界! 你好 ==2293== ==2293== 堆摘要: ==2293== 在退出时使用:0 个块中的 0 个字节 ==2293== 总堆使用量:4 个分配,4 个释放,31 个字节分配 ==2293== ==2293== 所有堆块都被释放——不可能有泄漏 ==2293== ==2293== 对于检测到和抑制的错误计数,重新运行:-v ==2293== 错误摘要:来自 1 个上下文的 1 个错误(抑制:来自 6 个的 17 个) %

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

【讨论】:

  • 令人印象深刻...我不知道存在这样的工具!但是我的主要工作站是一个 Windows 机器 :( 我在 SO 上进行了一些搜索,但没有找到任何(免费)工具与 Visual Studio 一样好。我可能必须设置一个 linux VM 才能利用如此高级的调试工具。
【解决方案3】:
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'

【讨论】:

    【解决方案4】:

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

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

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

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

    【讨论】:

    • 实际上,如果他只是删除第一个示例中的*p = '\0',它应该可以工作。 while (*p++ = *q++) 将复制零字符然后停止。 (我会说他应该停止这样做,并使用来自string.h 的东西,例如memcpy。一个更多的 C++ 人[而不是 C 人] 也会说他不应该打扰 char *并建议std::string。)
    • @asveikau:*p = '\0' 是您的正确选择。但如果这是一个练习,他应该手动进行。 (虽然我同意有人可能会质疑他为什么使用std::strlen(),而不是std::strcpy()。)
    • @sbi:同事,我也可以提供我自己的反对清单,但我的经验表明,人们大多在琐碎的情况下寻求帮助(我也是)。
    • 还有一件事——为什么我的程序在第一次尝试写入未分配的内存时没有崩溃?在析构函数中调用 delete[] 之前,它实际上不会崩溃。
    • @Darel,它与内存管理过程有关。根据英特尔的平面模型,当您需要 10 个字节时,要求操作系统(linux 或 windows)分配内存太贵了。这就是为什么所有工具(编译器、CLR ...)都会保留大块,从而减少对操作系统内存管理器的调用次数。如果您的内存错误触及预先分配的块,则不会引发异常。如果您尝试访问非托管操作系统内存(例如:0xCCCCCCCC),则会引发异常。最后关于删除:这个操作符尝试支持内存块的链表,因为你已经损坏了这个块引发了异常。
    猜你喜欢
    • 2014-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-18
    • 1970-01-01
    • 2014-03-31
    • 1970-01-01
    • 2020-08-18
    相关资源
    最近更新 更多