Valgrind утверждает, что в освобождении памяти слишком много освобождений - PullRequest
1 голос
/ 12 октября 2019

Я читаю Страуструпа "4-е издание на языке программирования c ++" и дошел до того, что он говорит о деструкторах.

Я попытался последовать примеру Страуструпа и проверил, доволен ли Вальгринд, но это не так:утверждает, что есть некоторые ошибки.

Я не совсем понимаю его комментарии, или где проблема может быть. Что я действительно понимаю, так это то, что Вэлгринд думает, что есть дополнительное свободное место, которое я не смог обнаружить.

Очень хотел бы, чтобы здесь помогли эксперты. Спасибо.

обратите внимание, что я использую try catch в этом конкретном примере, но я не думаю, что это проблема ... Что действительно важно в этом коде, так это, вероятно, конструкторы и деструкторы. Тем не менее, я выкладываю все для полноты

#include <iostream>
using namespace std;
class Vector
{
public:
    Vector();
    Vector(int s);
    ~Vector();
    double &operator[](int i);
    int size();
    static int get_default_size();

private:
    double *elem;
    int sz;
    static const int default_size = 5;
};

Vector::Vector()
{
    elem = new double[default_size];
    sz = default_size;
    for (int i = 0; i < sz; ++i)
    { //initialize elem array's values
        elem[i] = i;
    }
}

Vector::Vector(int s)
//: elem{new double[s]}, sz{s} //a dangerous idea - what if s is negative?
{
    if (s < 0)
        throw length_error{""};
    elem = new double[s];
    sz = s;
    for (int i = 0; i < sz; ++i)
    { //initialize elem array's values
        elem[i] = 0;
    }
}

Vector::~Vector()
{
    cout << "destructor working now" << endl;
    delete[] elem;
}

double &Vector::operator[](int i)
{
    return elem[i];
}

int Vector::size()
{
    return sz;
}

int Vector::get_default_size()
{
    return default_size;
}

Vector test()
{
    try
    {
        Vector v(-27);
        return v;
    }
    catch (std::length_error)
    {
        cout << "negative length" << endl;
        cout << "length will get default size, " << Vector::get_default_size() << endl;
        Vector v = Vector();
        return v;
    }
}

int main()
{
    Vector v = test();
    for (int i = 0; i < v.size(); ++i)
        cout << v[i] << endl;
}

Я ожидал, что valgrind покажет 0 ошибок, но это вывод, который я получаю от него:

<pre>==14640== Memcheck, a memory error detector
==14640== Copyright (C) 2002-2017, and GNU GPL&apos;d, by Julian Seward et al.
==14640== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==14640== Command: ./a.out
==14640== 
negative length
length will get default size, 5
destructor working now
==14640== Invalid read of size 8
==14640==    at 0x10933D: main (vector_improved1.cpp:83)
==14640==  Address 0x5b7e190 is 0 bytes inside a block of size 40 free&apos;d
==14640==    at 0x4C3173B: operator delete[](void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14640==    by 0x10913A: Vector::~Vector() (vector_improved1.cpp:45)
==14640==    by 0x109299: test() (vector_improved1.cpp:74)
==14640==    by 0x10930E: main (vector_improved1.cpp:81)
==14640==  Block was alloc&apos;d at
==14640==    at 0x4C3089F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14640==    by 0x108FBF: Vector::Vector() (vector_improved1.cpp:21)
==14640==    by 0x10927A: test() (vector_improved1.cpp:74)
==14640==    by 0x10930E: main (vector_improved1.cpp:81)
==14640== 
0
1
2
3
4
destructor working now
==14640== Invalid free() / delete / delete[] / realloc()
==14640==    at 0x4C3173B: operator delete[](void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14640==    by 0x10913A: Vector::~Vector() (vector_improved1.cpp:45)
==14640==    by 0x10937B: main (vector_improved1.cpp:81)
==14640==  Address 0x5b7e190 is 0 bytes inside a block of size 40 free&apos;d
==14640==    at 0x4C3173B: operator delete[](void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14640==    by 0x10913A: Vector::~Vector() (vector_improved1.cpp:45)
==14640==    by 0x109299: test() (vector_improved1.cpp:74)
==14640==    by 0x10930E: main (vector_improved1.cpp:81)
==14640==  Block was alloc&apos;d at
==14640==    at 0x4C3089F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==14640==    by 0x108FBF: Vector::Vector() (vector_improved1.cpp:21)
==14640==    by 0x10927A: test() (vector_improved1.cpp:74)
==14640==    by 0x10930E: main (vector_improved1.cpp:81)
==14640== 
==14640== 
==14640== HEAP SUMMARY:
==14640==     in use at exit: 0 bytes in 0 blocks
==14640==   total heap usage: 4 allocs, 5 frees, 73,912 bytes allocated
==14640== 
==14640== All heap blocks were freed -- no leaks are possible
==14640== 
==14640== For counts of detected and suppressed errors, rerun with: -v
==14640== ERROR SUMMARY: 6 errors from 2 contexts (suppressed: 0 from 0)

1 Ответ

1 голос
/ 12 октября 2019

Чтобы объяснить, почему это происходит, взгляните на эту тупую версию вашего кода:

#include <iostream>


class Vector {
public:
    Vector() {
        std::cout << "Vector constructor (this=" << std::hex << this << ")" << std::endl;
    }
    Vector(const Vector& other){
        std::cout << "Vector copy constructor (other=" << std::hex << (&other) << " this=" << this << ")" << std::endl;
    }
    Vector& operator=(const Vector& other){
        std::cout << "Vector assignment operator (other="  << std::hex << (&other) << " this=" << this << ")" << std::endl;
        return (*this);
    }
    ~Vector(){
        std::cout << "Vector destructor (this=" << this << ")" << std::endl;
    }
};

Vector test() {
    Vector o = Vector();
    return o;
}

int main() {
    Vector o = test();
    return 0;
}

И ее вывод при запуске:

Vector constructor (this=0x7ffee4a777d0)
Vector copy constructor (other=0x7ffee4a777d0 this=0x7ffee4a777d8)
Vector destructor (this=0x7ffee4a777d0)
Vector copy constructor (other=0x7ffee4a777d8 this=0x7ffee4a77810)
Vector destructor (this=0x7ffee4a777d8)
Vector copy constructor (other=0x7ffee4a77810 this=0x7ffee4a77818)
Vector destructor (this=0x7ffee4a77810)
Vector destructor (this=0x7ffee4a77818)

Как видите,вектор, изначально созданный в test(), копируется несколько раз через различные экземпляры (каждый созданный экземпляр также уничтожается). Всякий раз, когда это происходит в вашем коде, вызывается конструктор копирования по умолчанию, автоматически сгенерированный компилятором.

Конструктор копирования по умолчанию просто копирует каждый атрибут из исходного экземпляра в целевой экземпляр (включая double *elem). Семантика обычно неверна при работе с динамически выделяемой памятью, потому что несколько экземпляров «владеют» одними и теми же базовыми данными. Когда первый созданный вектор уничтожается, он освобождает elem, а остальные экземпляры указывают на висячую (недействительную) память.

Чтобы решить эту проблему, необходимо убедиться, что ваша реализация вектора включает в себя присваивание иСкопируйте конструктор, который фактически выделяет новую память, и скопируйте базовые данные из скопированного вектора в целевой вектор.

...