【问题标题】:How do I solve this memory leak如何解决此内存泄漏
【发布时间】:2020-04-07 09:06:01
【问题描述】:

我正在尝试重新创建向量类,并且我认为我的代码中存在内存泄漏,但我不知道如何解决它。使用我的 Visual Studio 中的 CRT 库,它告诉我,每次调用该保留时,假定的内存泄漏都会翻倍。

我不太清楚为什么会这样,或者是否存在内存泄漏。内存泄漏检测说它是保留函数中的这一行 int* temp = new int[n];

这就是我理解的储备功能中发生的事情:

一旦 arr 的内容被复制到 temp 中,删除 arr 就可以了。分配 arr = temp 应该可以工作,因为我所做的只是让 arr 指向与 temp 相同的位置。因为 arr 之前已被删除,所以我在堆中只有 1 个数组,并且 arr 和 temp 都指向同一个数组,所以应该没有内存泄漏。 Temp 无关紧要,因为它在退出范围后会消失。在随后调用 reserve 函数时,每件事都会重复,并且 arr 指向的堆中应该只有一个数组。

我确实相信我的想法在某些方面可能是错误的。

    #include "Vector.h"

    namespace Vector {

    vector::vector() {
        sz = 0;
        space = 0;
        arr = nullptr;
    }
    vector::vector(int n) {
        sz = n;
        space = n;
        arr = new int[n];
        for(int i = 0; i < n; i++) {
            arr[i] = 0;
        }
    }
    void vector::push_back(int x) {
        if(sz == 0) {
            reserve(1);
        } else if (sz == space) {
            reserve(2*space);
        }
        arr[sz] = x;
        sz++;
    }
    void vector::reserve(int n) {
        if (n == 1) {
            arr = new int[1]; //arr was a nullptr beforehand
        }
        int* temp = new int[n]; 
        for(int i = 0; i < n; i++) {
            temp[i] = arr[i];
        }
        delete[] arr;
        arr = temp;
        space = n;      
    }

【问题讨论】:

  • 如果我打电话给x.reserve(10); x.reserve(1); 怎么办?
  • Vector 被销毁时释放内存的析构函数不会受到伤害。但是一旦你添加了,不要忘记Rule of Three。
  • 当space 小于n 时,请留意for(int i = 0; i &lt; n; i++) { temp[i] = arr[i]; }。
  • 想一想——如果要预留的金额小于或等于已预留的金额,您的reserve 函数将无法实现自己的目的。如果请求的金额小于当前储备金,您应该只返回。我们还需要查看minimal reproducible example,展示您如何生成此内存泄漏。我们很容易获取您的代码并破坏您的vector 类,但我们需要看看您是如何破坏它的。
  • //arr was a nullptr beforehand。不,例如拨打reserve(1) 两次。

标签: c++ memory-leaks


【解决方案1】:

您的代码假定vector::reserve(int n) 中的arr 是null。

相反,可能会根据arr 是否为null 来揭示reserve 的功能。

void vector::reserve(int n) {
    if (arr) { //handle case when arr is null
        space += n;
        arr = new int[space];
        //no need to copy anything!
    } else { //handle case when arr is not null
        int* tmp(new int[space + n]);
        for(int i = 0; i < space; i++) {
            tmp[i] = arr[i];
        }
        delete[] arr;
        arr = tmp;
        space += n;
    }
}

此外,上面的代码假设您的意思是保留 space+n 而不是允许 reserve 缩小数组,因为如果您保留的数据少于之前的保留,您将丢失数据。在处理指针状态时,最好不要使用关于指针状态的假设,因为当您的代码变得更复杂时,这些假设最终可能会被遗忘或更模糊。

【讨论】:

    【解决方案2】:

    那里没有太多错误。

    错误

        if (n == 1) {
            arr = new int[1]; //arr was a nullptr beforehand
        }
    

    无法保证评论。没有什么能阻止多次调用调整大小,包括调用reserve(1),这将泄漏arr 指向的任何内存。而是考虑

        if (arr == nullptr) {
            arr = new int[n]; //arr was a nullptr beforehand
        }
    

    现在评论保证是真的。

    每次增加数组大小时,复制循环都会超出arr 的末尾。

        for(int i = 0; i < n; i++) {
            temp[i] = arr[i];
        }
    

    arr 只适用于 arr[sz-1]。如果n 大于space,而且几乎总是如此,arr[i] 就会进入Undefined Behaviour 的广阔天地。不是个好去处。

        for(int i = 0; i < n && i < sz; i++) {
            temp[i] = arr[i];
        }
    

    同时检查n 和sz 以防止两端溢出和复制尚未设置的数据。如果没有什么可复制的,一切都完成了。

    机会目标

    该类在销毁时需要一个析构函数来释放它拥有的任何内存 (What is ownership of resources or pointers?)。没有它,你就有泄漏。

    vector::~vector() {
        delete[] arr;
    }
    

    如果它有析构函数,Rule of Three 要求它有特殊的支持函数来处理(至少)类的复制或明确禁止复制。

    // exchanges one vector for the other. Generally useful, but also makes moves 
    // and assignments easy
    void vector::swap(vector& a, vector& b)
    {
        std::swap(a.sz, b.sz);
        std::swap(a.space, b.space);
        std::swap(a.arr, b.arr);
    }
    
    // Copy constructor
    vector::vector(const vector& src):
        sz(src.sz),
        space (src.space),
        arr(new int[space])
    {
        for(int i = 0; i < sz; i++) {
            arr[i] = src.arr[i];
        }
    }
    
    // move constructor
    vector::vector(vector&& src): vector()
    {
        swap(*this, src);
    }
    
    // assignment operator
    vector::vector& vector::operator=(vector src)
    {
        swap(*this, src);
    
        return *this;
    }
    

    复制构造函数使用Member Initializer List。这就是有趣的: 位。

    赋值运算符使用Copy and Swap Idiom。这不是实现赋值运算符的最快方法,但它可能是最简单的。从简单开始,只有在最简单的不符合要求时才努力。

    【讨论】:

      【解决方案3】:

      我也有同样的问题。我创建了两个指向堆中相同地址的指针。当我尝试过度释放内存时,结果只有一个指针可以做到这一点,它是指向该地址的第一个指针。指向该地址的第二个或第三个指针没有释放内存的权限,但只有第一个指针具有该权限。

      例子

      int *a = new int[5];
      int *b = a;
      int *c = a;
      

      指针 b 和 c 没有权限释放指向 a 的内存地址。因此,如果我说delete[] b 或delete[] c,内存没有被释放,他们没有这样做的权限。然后我尝试写delete [] a 并且成功了。我没有真正的答案,我只是试图通过我所做的尝试和错误来接近。这就是我得到的。

      实际上这种情况是违反规则的,但C++仍然允许我们这样做,它被称为未定义的行为。我们违反了delete[] 运算符的规则,但是 C++ 仍然允许你这样做,结果,你得到了意想不到的输出。

      【讨论】:

      • 您可以在您的示例中使用a、b 或c 来删除分配的对象;你不能做两次。正如您的回答所暗示的那样,您不仅受到使用 a 的限制。这是a、b 和c 持有的值,只能使用一次,而不是变量本身。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-06-28
      • 1970-01-01
      • 1970-01-01
      • 2021-11-18
      • 2018-04-11
      相关资源
      最近更新 更多