【问题标题】:Deleting arrays, or deallocating memory, C++ bug删除数组或释放内存,C++ 错误
【发布时间】:2015-10-15 19:00:03
【问题描述】:

下面是我正在尝试实现的堆栈数据结构的代码的 sn-p。

由于某种原因,当我删除 currentArray 时,newArray 也被删除了,因为下面的代码给了我一个运行时错误,其中 newArraycurrentArray 的内容是垃圾值。

我不确定为什么会这样。

非常感谢任何关于我为什么会遇到这个错误的见解,以及我下面的push() 实现是否从基本角度来看是正确的。

// Destructor
~Stack() 
{
    if ((size > 0) && (currentArray) != NULL && (newArray != NULL))
    {
        delete[] currentArray;
        delete[] newArray;
    }
}

inline void push(T value)
{
    if (isEmpty())
    {
        // Stack size is increased by 1
        size++;

        currentArray = new T[size];
        currentArray[size - 1] = value;
    }
    else
    {
        // Stack size is increased by 1
        size++;

        // Create the new array
        newArray = new T[size];

        // Copy the contents of the old array into the new array
        copy(currentArray, currentArray + size, newArray);

        // Push the new value on to the new array
        newArray[size - 1] = value;

        // Copy the new array into the current
        currentArray = new T[size];
        currentArray = newArray;
    }
}

【问题讨论】:

  • 首先,请发布一个完整但很小的程序来演示错误。其次,你的Stack 的析构函数看起来很奇怪——为什么你需要所有这些条件来delete[] 内存?只需delete[] 即可,无需所有这些条件。
  • 另外,你的push函数中的delete[ ]在哪里摆脱旧记忆?如果currentArray 已分配,则您已创建内存泄漏。这就是为什么我们需要查看所有内容,而不是 sn-p。我敢打赌,你在这门课上遇到的麻烦要早得多,而且是你没有向我们展示的代码。
  • 优化说明。当增加堆栈的大小时,不要只是将大小增加一,因为每次添加时复制堆栈所花费的时间会影响您的性能。将大小增加一些合理的数字。甚至可能翻倍。

标签: c++ memory-management data-structures stack dealloc


【解决方案1】:

首先,您不需要析构函数中的检查。

~Stack() {
    delete [] currentArray;
    delete [] newArray;
}

推送有几个问题:

  1. 您通过值传递value,这可能很昂贵。您应该通过引用传递。
  2. 你在增加copy() 之后使用size,这意味着你将原始内存复制到newArray 的最后一个槽中。这可能是良性的(T = int + 一点运气)或灾难性的(T = std::string)。
  3. newArray 仅在 push() 中需要。它应该是一个局部变量,而不是一个成员变量。
  4. 您每次增长 1,这导致 O(n2) 时间来填充。您应该以几何级数增长,这需要一个额外的容量成员变量。
  5. 您调用了两次new T[size]。其中一个泄漏。

这是一个重新设计的版本:

class Stack {
public:
    …
    inline void push(const T& value) {
        if (size_ == capacity_) {
            capacity_ = capacity_ ? capacity_ * 2 : 1;
            T* dest = new T[capacity_];
            copy(data_, data_ + size_, dest);
            delete [] data_;
            data_ = dest;
        }
        data_[size_++] = value;
    }
    …
private:
    T* data_ = nullptr;
    size_t size_ = 0, capacity_ = 0;
};

这绝不是一段可靠的工业级代码。它没有解决异常安全、避免复制(除其他外,需要额外的重载:push(T&& value))和一系列其他细微之处。事实上,我什至没有检查它是否编译!它可能适用于一个玩具项目,但在现实世界中你应该简单地使用 std::stack。

【讨论】:

  • "你在增加后在 copy() 中使用 size,这意味着你将原始内存复制到 newArray 的最后一个槽中。这可能是良性的(T = int + 一点运气)或灾难性的(T = std::string)。”你能解释一下吗,因为我确实打算使用字符串。此实现是实现词法分析的前导。您是否也可以解释这一点“您每次增长 1,这会导致 O(n2) 时间来填充。您应该以几何方式增长,这需要一个额外的容量成员变量。”谢谢。
  • 用char*代替string会更好吗?
  • @Andre 最好不要尝试复制未分配的空间。无论如何,char * 的向量会引起更多的头痛。
  • @Andre 解释第一点,如果你有 4 个元素,并尝试推送第 5 个元素,你的代码首先增加大小,然后调用 copy(currentArray, currentArray + size, …),实际上是 copy(currentArray, currentArray + 5),但是您的源数组此时只有四个元素。因此,在考虑插入实际的第 5 个元素之前,您将尝试复制不存在的第 5 个元素。
  • @Andre Wrt 几何增长:想象一下插入 10 个元素。使用您的方法,第一次插入就可以了。第二次插入需要复制第一个元素。第三次插入将复制前两个元素,依此类推。插入所有 10 个元素将花费 1+2+3+4+5+6+7+8+9 = 45 个副本。通常,n 次插入需要 n(n - 1)/2 份,即 O(n²)。将每个不适合的插入的数组大小加倍效率更高。类似的分析表明,n 次插入只需要 O(n) 个副本。
【解决方案2】:

在您的 push 函数中,在您执行的 else 块中

currentArray = newArray;

所以你的currentArraynewArray 都指向同一个东西! (你自己做的)。

因此,当您执行delete [] currentArray 时,您无需删除newArray,因为该内存已被删除。这就是为什么你的程序会出错,你试图删除已经被删除的内存!!

此外,在你的推送功能中,你会做 ::

currentArray = new T[size];
currentArray = newArray;

我相信你的classcurrentArray 是一个pointernew 也返回指向新分配内存的指针。所以,如果你想让你的currentArray 指向newArray,你只需要在不为currentArray 分配内存的情况下执行currentArray = newArray

此外,你也这样做::

 newArray = new T[size];
 copy(currentArray, currentArray + size, newArray);

在您的 if 块内,我相信您正在将数据 currentArray 复制到 newArray 因此,在将数据从 currentArray 复制到 newArray 之后,您需要将 delete 先前分配的内存分配给currentArray,否则是内存泄漏(程序未能释放丢弃的内存,导致性能受损或失败)delete 没用的内存总是好的。

所以,我建议您在此之后添加 delete [] currentArray 声明。

所以,你的最终代码应该是这样的 ::

// Destructor
~Stack() 
{
    /*
     *if ((size > 0) && (currentArray) != NULL) --> 
     *deleting a NULL pointer is completely defined!
     *So, in your constructor if you are initializing 
     *currentArray to NULL you need not have any if condition.
    */
    delete[] currentArray;
}

inline void push(T value)
{
    if (isEmpty())
    {
        size++;

        currentArray = new T[size];
        currentArray[size - 1] = value;
    }
    else
    {
        size++;

        newArray = new T[size];

        copy(currentArray, currentArray + size, newArray);

        delete [] currentArray; //EDIT

        newArray[size - 1] = value;

        //currentArray = new T[size]; --> Deleted Line
        currentArray = newArray;
    }
}

注意 :: 万一您的编译器符合 C++11,您应该考虑使用 nullptr 而不是 NULL,原因如下:What are the advantages of using nullptr?

【讨论】:

  • 如果析构函数需要条件来进行删除,那么这个类就存在根本缺陷。析构函数应该能够在没有任何条件的情况下调用内存上的delete []。换句话说~Stack() { delete [ ] currentArray; } 如果这会导致问题,那么这个类就是错误的。
  • 是的.. 我刚刚从那里复制了代码。如果 currentArray 被初始化为 NULL 可能删除 if 条件不会有什么不同!
  • 滑稽。 @Bond 编辑了 OP 的问题。 User007 已回答。
  • 谢谢。这行得通。我之前正在删除新数组,但出现错误,这就是我将其移至析构函数的原因。无论如何,它现在似乎正在工作。谢谢你。在防止内存泄漏方面,您建议我为 pop() 做什么。
  • @Andre 您必须为此显示pop() 代码。 什么是内存泄漏? :: 程序未能释放废弃的内存,导致性能受损或失败。(以防万一你不清楚什么是内存泄漏)
【解决方案3】:

也许我遗漏了一些东西,但如果 if 声明被证明是真的,它无论如何都会删除两者。

另外,从基本的角度来看,你为什么不拥有:

if ((size > 0) && (currentArray != NULL) && (newArray != NULL))

这不是什么大问题,但不匹配的括号是敌人。

【讨论】:

    【解决方案4】:

    @user007 这是我的 pop() 函数。

    // Remove an element from the stack
    inline const T& pop()
    {
        if (size > 0)
        {
            T value;
    
            size--;
    
            value = data[size];
    
            T* dest = new T[size];
    
            // Copy the contents of the old array into the new array
            copy(data, data + (size), dest);
    
            delete[] data;
    
            data = dest;
    
            return value;
        }
    
        return NULL;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-03-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-15
      • 2013-06-05
      • 1970-01-01
      相关资源
      最近更新 更多