2

我一直试图弄清楚现在几个小时,而我在我的智慧结束。如果有人能在我做错事的时候告诉我,我一定会很感激。实施运营商时使用删除[](堆损坏)+ =

我写了一个简单的类来模拟字符串的基本功能。类的成员包括字符指针数据(其指向动态创建的字符数组)和一个整数了strsize(其包含字符串,SANS终止子的长度。)

由于我使用删除,我已经实现了复制构造函数和析构函数。当我尝试实施运营商+ =时,会出现我的问题。 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; 
} 

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

+0

这引出了一些显而易见的问题 - 或“家庭作业”标签。 – sbi 2010-05-02 18:20:26

回答

4

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

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

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

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

已验证 - 谢谢! – Darel 2010-05-02 18:11:51

+2

实际上,如果他只是在第一个例子中删除'* p ='\ 0'',它应该可以工作。 'while(* p ++ = * q ++)'将复制零字符,然后停止。 (我会说他应该停止这样做,并使用'string.h'中的东西,例如'memcpy'。一个更多的C++人(与C人相对)也会说他不应该打扰' char *',并建议使用'std :: string'。) – asveikau 2010-05-02 18:39:40

+0

@asveikau:你现在用'* p ='\ 0''。但如果这是一个练习,他应该手动完成。 (虽然我同意可以质疑他为什么使用'std :: strlen()',而不是'std :: strcpy()'。) – sbi 2010-05-02 19:03:31

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' 
+0

Valgrind捕获了'* p ='\ 0''行上的错误。 – 2010-05-02 18:08:15

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<<str[1];

(如果有人碰到这个未来能想到更多的提示,随意扩大本)

+0

感谢您的提示。这本身不是家庭作业;我是一名在职学习C++的专业人员。这来自“加速C++”问题12-1。 – Darel 2010-05-02 18:59:03

+0

啊,_加速C++ _,一个很好的选择,虽然它有一个陡峭的学习曲线。一定要检查下一个C++书籍的权威C++书籍列表:http://stackoverflow.com/questions/388242/。 – sbi 2010-05-02 19:05:36

3

已经有一群很好的答案在这里,但值得把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)的线。

+0

令人印象深刻...我不知道这些工具存在!但我的主工作站是一个Windows盒子:(我在这里搜索了一些东西,但没有发现任何(免费)工具与Visual Studio一样好。我可能必须建立一个Linux虚拟机才能利用这样的高级调试工具。 – Darel 2010-05-04 14:20:57