【问题标题】:C++ Heap Corruption but only in Unit Test ProjectC ++堆损坏,但仅在单元测试项目中
【发布时间】:2020-02-05 18:12:13
【问题描述】:

我正在使用模板实现自定义堆栈。但是我遇到了一个让我质疑内存安全的问题。析构函数在正常使用时工作正常,但在单元测试时会触发堆损坏。特别是在期望抛出异常的测试上。

所有堆栈代码。

#pragma once
#include "StackOverflowException.h"
#include "StackEmptyException.h"

template <class T>
class Stack
{
public:
    const static int MAX_SIZE = 1000;

    Stack() : m_array(new T[MAX_SIZE]), m_size(0), m_top(0) {};
    Stack(const Stack<T>& _other)
    {
        m_array = new T[MAX_SIZE];
        for(auto i = 0; i < _other.size(); ++i)
        {
            m_array[i] = _other.m_array[i];
        }
        m_size = _other.size();
        m_top = _other.m_top;
    }

    ~Stack()
    {
        delete[] m_array;
    }

    void push(T _item)
    {
        if (m_size + 1 == MAX_SIZE + 1)
        {
            throw StackOverflowException();
        }
        m_array[++m_top] = _item;
        ++m_size;
    }

    T pop()
    {
        if(m_top != 0)
        {
            --m_size;
            T item =  m_array[m_top];
            --m_top;
            return item;
        }
        throw StackEmptyException();
    }

    T peek() const
    {
        if (m_size == 0) throw StackEmptyException();
        T item = m_array[m_top];
        return item;
    }

    bool empty() const
    {
        return m_size == 0;
    }

    size_t size() const
    {
        return m_size;
    }
private:
    T* m_array;
    size_t m_size;
    int m_top;
};

导致堆损坏的单元测试。


        TEST_METHOD(CustomStackPopException)
        {
            Stack<int> stack;
            Assert::ExpectException<StackEmptyException>([&] { stack.pop(); });
        }

        TEST_METHOD(CustomStackPeekException)
        {
            Stack<int> stack;
            Assert::ExpectException<StackEmptyException>([&] { int a = stack.peek(); });
        }

        TEST_METHOD(CustomStackOverflowException)
        {
            Stack<int> stack;
            const auto functor = [&]
            {
                for(auto i = 0; i <= Stack<int>::MAX_SIZE; ++i)
                {
                    stack.push(i);
                }
            };
            Assert::ExpectException<StackOverflowException>(functor);
        }

当它到达第一个测试时,它会弹出一个警告说:

HEAP CORRUPTION DETECTED: after Normal block(#279) at 0x0000020298CBC60.
CRT detected that the application wrote to memory after end of heap buffer.

我尝试在堆上创建堆栈对象,但这会导致同样的错误。

【问题讨论】:

  • 您的Stack 用户定义的赋值运算符在哪里?没有它,您的课程无法安全复制。 Stack&amp; operator=(const Stack&amp;) -- 这就是完成“规则3”需要实现的功能。
  • 正确,我打算实施五法则,但之前想解决这个问题。
  • m_sizem_top 始终具有相同的值。 (其中之一是不必要的,因为您希望顶部索引比元素数少一。)
  • 正如@VladFromMoscow 所指出的,push 方法正在访问其分配的内存之外的内存,这会导致CustomStackOverflowException 测试中的内存损坏。在您尝试删除它之前,通常不会发现内存损坏。这可能是你的问题。
  • @Styn -- 缺少用户定义的赋值运算符也可能导致堆损坏。那些TEST_METHOD 不是标准宏,因此关于为什么会出现堆损坏的所有可能性都摆在桌面上。

标签: c++ memory-management stack heap-corruption


【解决方案1】:

这个成员函数

void push(T _item)
{
    if (m_size + 1 == MAX_SIZE + 1)
    {
        throw StackOverflowException();
    }
    m_array[++m_top] = _item;
    ++m_size;
}

已经不正确。假设 MAX_SIZE 等于 1,m_size 等于 0。在这种情况下,m_size + 1 不等于 MAX_SIZE + 1 并且有 writtem

    m_array[1] = _item;

在分配的数组之外。

编辑:按照以下方式更改方法后

  void push(T _item)
    {
        if (m_size + 1 > MAX_SIZE)
        {
            throw StackOverflowException();
        }
        m_array[++m_top] = _item;
        ++m_size;
    }

在我指出它的问题后,实际上该方法也有同样的问题。事实上,你什么都没有。

你的堆栈也没有用索引 0 填充项目。所以在构造函数中

Stack(const Stack<T>& _other)
{
    m_array = new T[MAX_SIZE];
    for(auto i = 0; i < _other.size(); ++i)
    {
        m_array[i] = _other.m_array[i];
    }
    m_size = _other.size();
    m_top = _other.m_top;
}

在位置 0 处存在对具有不确定值的项的访问

成员函数push应该这样定义

void push( const T &_item)
{
    if (m_size == MAX_SIZE)
    {
        throw StackOverflowException();
    }
    m_array[m_top++] = _item;
    ++m_size;
}

其他成员函数要根据成员函数push来改变。

【讨论】:

  • 谢谢,我已对其进行了编辑并相应地更新了我的问题。
  • @Styn 在出现显示问题的答案后以这种方式更改问题是个坏主意。
  • 抱歉,我已恢复更改。
【解决方案2】:

感谢用户的组合,问题已得到解决。是 push 函数导致了错误。

推送功能改自:

  void push(T _item)
    {
        if (m_size + 1 == MAX_SIZE + 1)
        {
            throw StackOverflowException();
        }
        m_array[++m_top] = _item;
        ++m_size;
    }

收件人:

    void push(T _item)
    {
        if (m_size == MAX_SIZE)
        {
            throw StackOverflowException();
        }
        m_array[m_size] = _item;
        ++m_size;
        ++m_top;
    }

构造函数已更改为:

    Stack() : m_array(new T[MAX_SIZE]), m_size(0), m_top(0) {};

    Stack() : m_array(new T[MAX_SIZE]), m_size(0), m_top(-1) {};

现在size和top已经不一样了,m_top实际上指向了栈顶。

【讨论】:

  • 由于栈顶总是m_size - 1,所以我建议只使用m_size。这将最大程度地降低错误风险并简化代码。
【解决方案3】:

使用分配号

HEAP CORRUPTION DETECTED: after Normal block(#279) at 0x0000020298CBC60.
CRT detected that the application wrote to memory after end of heap buffer.

然后在某处启动插入

_CrtSetBreakAlloc(279);

这将向您显示内存分配的位置,并且应该提供有关损坏位置的重要提示。

为什么要自己动手卷?

不要自己实现stack

STL 已经有了那个容器

template <class T>
using my_stack = std::stack< T, std::vector<T> >;

【讨论】:

  • @engf-010 我不同意。我认为这是该语言不像它可能的那样受欢迎的原因之一。出于同样的原因 - 您不必成为机械师也能开车。
  • 您实际上是第一个不同意练习不好的人。许多 C++ 学习书籍包含字符串/向量/堆栈/队列/等的实现,只是为了获得如何做事的经验。除此之外,标准解决方案可能不足以满足人们的需求。
  • 对于新手来说,实现广泛使用的数据结构的版本只是为了获得“感觉”是一种很好的做法。
  • @engf-010 -- 获得“感觉”的风险是让新 C++ 学生放弃 C++ 并转到其他语言,在那里他们实际看到正在构建和运行的具体程序。谁愿意花时间学习如何编写向量类或链表(直到今天,我还没有看到 C++ 学生正确地完成它),当他们可以得到实际做一些有形的编程任务时?如果学生使用 STL,就好像 C++ 不是自己写的——他们仍然需要把逻辑放在一起。
  • @PaulMcKenzie:你并没有完全错,我部分同意。在 C++ 中使用 STL 是必须的。但实施链表是 IMO 的一项要求。一些学生不能做到这一点,更多地说明了学生和他们的老师,而不是有效的论点。我能做到,所以它不像火箭科学。
猜你喜欢
  • 1970-01-01
  • 2013-07-06
  • 2018-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-03
  • 2011-08-03
相关资源
最近更新 更多