Error trying to delete a dynamic allocated matrix using the destructor

1

As the title says i try to delete a dynamic allocated matrix using the destructor and i get the following error:

Exception thrown at 0x78D8DB1B (ucrtbased.dll) in oop.exe: 0xC0000005: Access violation reading location 0xDDDDDDCD.

Here is the code that i try to run.

    #include <iostream>
    using namespace std;

     template<class T>
    class Matrice
    {
    private:
        int marime;
        T** matrice;
    public:
        Matrice(int marime);
        ~Matrice();
        friend istream& operator>>(istream& in,Matrice<T>& mat) {
            for (int i = 0; i < mat.marime; i++) {
                for (int j = 0; j < mat.marime; j++) {
                    cout << "Matrice[" << i << "][" << j << "]: ";
                    in >> mat.matrice[i][j];
                }
            }
            return in;
        }
        friend ostream& operator<<(ostream& out,Matrice<T> mat) {
            for (int i = 0; i < mat.marime; i++) {
                cout << endl;
                for (int j = 0; j < mat.marime; j++) {
                    out << mat.matrice[i][j]<<" ";
                }
            }
            return out;
        }
    };

    template<class T>
    Matrice<T>::Matrice(int marime) {
        this->marime = marime;
        matrice = new T * [marime];
        for (int i = 0; i < marime; i++) {
            matrice[i] = new T[marime];
        }
    }
    template<class T>
    Matrice<T>::~Matrice() {
        for (int i = 0; i < marime; i++) {
            delete[] matrice[i]; //Here is where i get the error.
        }
        delete[] matrice;
    }

int main()
{
    Matrice<int> test(3);
    cin >> test;
    cout << test;
}
c++
asked on Stack Overflow May 9, 2020 by Lungu Dragos

1 Answer

1

The memory address 0xddddddcd is a likely sign of a use-after-free bug, since Visual C++ debug builds tag all freed memory with that memory pattern. I compiled your program using ASAN on Linux (clang++ matrice.cc -g -Og -fsanitize=address) and was able to replicate your issue with the following stacktrace:

==6670==ERROR: AddressSanitizer: heap-use-after-free on address 0x603000000010 at pc 0x0000004c9a76 bp 0x7fffdcd001b0 sp 0x7fffdcd001a8
READ of size 8 at 0x603000000010 thread T0
    #0 0x4c9a75 in Matrice<int>::~Matrice() /tmp/z.cc:44:22
    #1 0x4c93c9 in main /tmp/z.cc:54:1
    #2 0x7f34a76370b2 in __libc_start_main /build/glibc-YYA7BZ/glibc-2.31/csu/../csu/libc-start.c:308:16
    #3 0x41f3cd in _start (/tmp/a.out+0x41f3cd)

0x603000000010 is located 0 bytes inside of 24-byte region [0x603000000010,0x603000000028)
freed by thread T0 here:
    #0 0x4c736d in operator delete[](void*) (/tmp/a.out+0x4c736d)
    #1 0x4c93c1 in main /tmp/z.cc:53:5
    #2 0x7f34a76370b2 in __libc_start_main /build/glibc-YYA7BZ/glibc-2.31/csu/../csu/libc-start.c:308:16

previously allocated by thread T0 here:
    #0 0x4c6b1d in operator new[](unsigned long) (/tmp/a.out+0x4c6b1d)
    #1 0x4c9536 in Matrice<int>::Matrice(int) /tmp/z.cc:36:19

It looks like some resource is read by the destructor at line 44 col 22 (delete[] matrice[i];) after it has already been freed by a destructor called from line 53 (cout << test).

The reason for this is easy to miss at first. The destructor was being called twice, once after cout << test and again at the end of main.

The issue is as follows: the function friend istream& Matrice::operator>> takes a parameter of type Matrice<T>&, which is fine, while operator<< takes just a Matrice<T> by value. This causes your instance of test to be copied, by the default copy constructor. This is a problem, because the default copy constructor doesn't deep-copy your arrays, but it just copies the pointers themselves.

When the private copy of test used in operator<< is destructed, it frees the same arrays that were used by test; thus when the destructor of test runs, it tries to read that already-freed array.

This hits the notion of the rule of 5/3/0: if your class requires a custom destructor, a custom copy constructor, or a custom operator=, it almost certainly needs all three. The cleanest way to resolve this would be a copy constructor that deep-copies the contents of the matrix to a new set of arrays. You could alternatively delete the copy constructor (Matrice(Matrice<T> const&) = delete), but this makes your class a little less flexible to use.

answered on Stack Overflow May 9, 2020 by nanofarad

User contributions licensed under CC BY-SA 3.0