字符串类中的字符串(const char *)构造函数内存泄漏

Posted

技术标签:

【中文标题】字符串类中的字符串(const char *)构造函数内存泄漏【英文标题】:String(const char*) constructor memory leak in string class 【发布时间】: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 tchars;

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

    return 0;

【问题讨论】:

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

以上是关于字符串类中的字符串(const char *)构造函数内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

C++ 我将一个const char*的字符串转换为string出错

无法将 const char 转换为构造字符串的字符串 [重复]

构造函数对 const C 字符串数组的帮助

java中String类的构造函数

C 中的字符串文字与 const char*

类数组内部的C++深拷贝const char *?