【问题标题】:String(const char*) constructor memory leak in string class字符串类中的字符串(const char *)构造函数内存泄漏
【发布时间】:2020-04-06 20:00:49
【问题描述】:

所以我试图实现 String 类,但我收到一个错误,说分段错误。我认为我的构造函数中存在内存泄漏。你能告诉我我做错了什么吗?谢谢。

这是构造函数代码,我不能使用任何标准库功能。

String(const char* chars){
            int i = 0;
            if(chars){
                while(chars[i]){
                    i++;
                }
            }
            len = i;
            str = new char[len - 1];
            for(int j = 0; j < len; j++){
                str[j] = chars[j];
            }
        };

这也是我的完整代码:

#include <iostream>

using namespace std;

class String{
    public:
        char* str;
        int len;
        String(){
            str = new char[0];
            len = 0;
        };

        String(const char* chars){
            int i = 0;
            if(chars){
                while(chars[i]){
                    i++;
                }
            }
            len = i;
            str = new char[len - 1];
            for(int j = 0; j < len; j++){
                str[j] = chars[j];
            }
        };

        String(const String& s){
            if(s.isEmpty() == false){
                len = s.len;
                str = new char[len];
                for(int i = 0; i < len; i++){
                    str[i] = s.str[i];
                }
            }
        };
        ~String() noexcept{
            if(len > 0)
            delete[] str;
        };

        bool isEmpty() const noexcept{
            if(len == 0){
                return true;
            }
            else{
                return false;
            }
        }

        unsigned int length() const noexcept{
            return len;
        }

        const char* toChars() const noexcept{
            char* temp = new char[len];
            int c = 0;
            while(temp[c] != '\0'){
                temp[c] = str[c];
                c++;
            }
            return temp;
        }
};

int main()
{
    const char* chars = "Boo is snoring";
    String s;
    String t{chars};

    cout << "t.len : " << t.length() << endl << "toChar() : " << t.toChars() << endl; 

    return 0;
}

【问题讨论】:

  • 您是否打算在表达式+ 中使用str = new char[len - 1];?您不会复制终止零字符。
  • 您的代码中有几个缺陷——其中一个是您没有在复制构造函数中设置所有成员变量。您不应该关心传入的字符串是否为空,您应该制作一个副本。此外,您还缺少用户定义的赋值运算符。

标签: c++ string memory


【解决方案1】:

String(const char* chars) 构造函数的问题在于以下语句:

str = new char[len - 1];

您正在分配 less 内存然后您需要,因此您的 for 循环超出了分配内存的范围。您需要分配至少 len 字符数,而不是len - 1 字符数:

str = new char[len];

如果您打算将str 以null 结尾,则需要分配len + 1 的字符数,然后在循环结束后插入一个null 终止符:

str = new char[len + 1];
for(int j = 0; j < len; j++){
    str[j] = chars[j];
}
str[len] = '\0';

您在 toChars() 方法中犯了类似的错误。您不会以空值终止输出,这是 operator&lt;&lt; 所要求的,您随后将内存传递给:

const char* toChars() const {
    char* temp = new char[len + 1];
    for(int c = 0; c < len; ++c){
        temp[c] = str[c];
    }
    temp[len] = '\0';
    return temp;
}

请注意,我删除了 toChars() 上的 noexcept。这是因为new[] 不是noexcept,如果它无法分配内存,它会抛出std::bad_alloc 异常,而你没有捕捉到。 noexcept 表示该方法根本没有throw 任何异常,但这里不是这种情况。

您的代码也存在其他问题:

  • 如果 len 为 0,即调用 Default 构造函数,或者使用 null/空字符串调用 Converting 构造函数,则析构函数会泄漏内存。如果你调用new[],你需要调用delete[],不管使用的是len

  • 如果要复制的 String 为空,则您的 Copy 构造函数未初始化 strlen

  • main() 中,你不是delete[]'ing toChars() 返回的内存。

  • 对于这种特殊情况是可选的,但一般来说,您缺少一个复制赋值运算符。而且,由于您显然使用的是 C++11 或更高版本,因此您还应该添加一个 Move 构造函数和一个 Move Assignment 运算符。请参阅Rule of 3/5/0

试试这个(根据请求不添加额外的库函数):

#include <iostream>

using namespace std;

class String {
    public:
        char* str = nullptr;
        unsigned int len = 0;

        String() = default;

        String(const char* chars) {
            if (chars) {
                unsigned int i = 0;
                while (chars[i]) {
                    ++i;
                }
                len = i;
                str = new char[len];
                for(int j = 0; j < len; ++j) {
                    str[j] = chars[j];
                }
            }
        }

        String(const String& s) {
            if (!s.isEmpty()) {
                len = s.len;
                str = new char[len];
                for(int i = 0; i < len; ++i){
                    str[i] = s.str[i];
                }
            }
        }

        ~String() noexcept {
            delete[] str;
        }

        String& operator=(const String &s) {
            if (&s != this) {
                String tmp(s);
                char *tmpstr = tmp.str;
                unsigned int tmplen = tmp.len;
                tmp.str = str;
                tmp.len = len;
                str = tmpstr;
                len = tmplen;
            }
            return *this;
        }

        bool isEmpty() const noexcept {
            return (len == 0);
        }

        unsigned int length() const noexcept {
            return len;
        }

        const char* toChars() const {
            char* temp = new char[len + 1];
            for(unsigned int c = 0; c < len; ++c) {
                temp[c] = str[c];
            }
            temp[len] = '\0';
            return temp;
        }
};

int main()
{
    String t{"Boo is snoring"};

    const char *chars = t.toChars();
    cout << "t.len : " << t.length() << endl << "toChar() : " << chars << endl; 
    delete[] chars;

    return 0;
}

虽然,实现toChars() 的更简单方法如下所示:

#include <iostream>

using namespace std;

class String {
    public:
        char* str = nullptr;
        unsigned int len = 0;

        String() = default;

        String(const char* chars) {
            if (chars) {
                unsigned int i = 0;
                while (chars[i]) {
                    ++i;
                }
                len = i;
                str = new char[len + 1];
                for(int j = 0; j < len; ++j) {
                    str[j] = chars[j];
                }
                str[len] = '\0';
            }
        }

        String(const String& s) {
            if (!s.isEmpty()) {
                len = s.len;
                str = new char[len + 1];
                for(int i = 0; i < len; ++i){
                    str[i] = s.str[i];
                }
                str[len] = '\0';
            }
        }

        ~String() noexcept {
            delete[] str;
        }

        String& operator=(const String &s) {
            if (&s != this) {
                String tmp(s);
                char *tmpstr = tmp.str;
                unsigned int tmplen = tmp.len;
                tmp.str = str;
                tmp.len = len;
                str = tmpstr;
                len = tmplen;
            }
            return *this;
        }

        bool isEmpty() const noexcept {
            return (len == 0);
        }

        unsigned int length() const noexcept {
            return len;
        }

        const char* toChars() const noexcept {
            return str ? str : "";
        }
};

int main()
{
    String t{"Boo is snoring"};

    cout << "t.len : " << t.length() << endl << "toChar() : " << t.toChars() << endl; 

    return 0;
}

【讨论】:

  • 感谢您的回答!但是我忘了告诉你我不能添加任何其他库。我不允许使用任何标准库功能。你能告诉我其他方式吗?谢谢!
  • @JaeyeonPark 我添加的库函数并没有消除我在原始代码中描述的错误。但无论如何,我已经更新了答案中的代码,只修复了你的错误,没有使用添加的库函数。
【解决方案2】:

你会崩溃,因为你分配了 len - 1 个字符 new char[len - 1],但复制了 len 字符 for(int j = 0; j &lt; len; j++) 并且最后不复制零字符。

第一个构造函数应该是

String(const char* chars) {
  len = 0;
  if (chars) {
    while (chars[len])
      len++;
  }
  str = new char[len + 1];
  for (int j = 0; j < len; j++){
    str[j] = chars[j];
  }
  str[len] = 0;
};

希望您能够正确更新第二个构造函数。

析构函数应该是

~String() noexcept{
  delete[] str;
}

希望您能够解决其他问题。

【讨论】:

  • 您的构造函数没有考虑charsnullptr 的可能性。 std::strlen()nullptr 有未定义的行为,当它是 nullptr 时,您的循环超出了 chars 的范围。此外,应该使用std::copy() 而不是std::memcpy()copy() 针对char 等琐碎类型进行了优化,并且可能会在内部使用memcpy()
  • 感谢您的回答!但是我忘了告诉你我不能添加任何其他库。我不允许使用任何标准库功能。你能告诉我其他方式吗?谢谢!
  • 好的,有什么问题,你已经演示了strlen的正确实现。
猜你喜欢
  • 2014-08-23
  • 2013-02-04
  • 2020-11-14
  • 1970-01-01
  • 2014-04-30
  • 2021-01-07
  • 2011-08-29
  • 2013-04-03
  • 1970-01-01
相关资源
最近更新 更多