double free or corruption (out) - Using C++ matrices

Asked

Viewed 311 times

1

I’m writing a class that deals with C++ matrices. However when creating a method to calculate the determinant of a matrix I am bumping into the following error:

double free or corruption (out)

Below, the file with the definitions of class methods:

#include "Matriz.hpp"
#include <iostream>
#include <cassert>
#include <cmath>


//Construtor para uma matriz que não rebece nenhum parametro de entrada. Dessa forma ela aloca memória para uma matriz 3x3, com todos os elementos iguais a zero.
Matriz::Matriz() {
    mColunas = 3;
    mLinhas = 3;
    mData = new double* [mLinhas];
    for (int i = 0; i < mColunas; i++)
    {
        mData[i] = new double [mColunas];
    }    
}

//Construtor para uma matriz quadrada (NxN), alocando memória para uma matriz de ordem N, tal que N é o número de elementos (n_elementos).
Matriz::Matriz(int n_elementos) {
    assert(n_elementos > 0);
    mColunas = n_elementos;
    mLinhas = n_elementos;
    mData = new double* [mLinhas];
    for (int i = 0; i < mColunas; i++)
    {
        mData[i] = new double [mColunas];
    }
}

//Construtor que recebe o número de colunas (n_colunas) e o número de linhas (n_linhas) da matriz e aloca memória com base nesses valores.
Matriz::Matriz(int n_linhas, int n_colunas) {
    assert(n_linhas > 0 && n_colunas > 0);
    mColunas = n_colunas;
    mLinhas = n_linhas;
    mData = new double* [mLinhas];
    for (int i = 0; i < mColunas; i++)
    {
        mData[i] = new double [mColunas];
    }
}

//Sobrescreve o destrutor do objeto, de modo que a memória seja devidamente desalocada;
Matriz::~Matriz() {
    for (int i = 0; i < mLinhas; i++)
    {
        delete[] mData[i];
    }
    delete[] mData;
}

//Retorna o numero de linhas da matriz
int Matriz::GetNumLinhas() const {
    return mLinhas;
}

//Retorna o numero de colunas da matriz
int Matriz::GetNumColunas() const {
    return mColunas;
}

//Retorna o determinante de uma determinada matriz quadrada A usando o método de Laplace. Se as a matriz for de ordem 1, o valore retornado será o unico elemento que ela possui. 
double Matriz::CalculaDeterminante() {
    assert(mLinhas == mColunas);
    double determinante, soma;
    if (mLinhas == 1) {
        determinante = mData[0][0];
    } 
    else {
        Matriz MatAux(mLinhas-1, mColunas-1);
        soma = 0.0;
        for (int coluna = 0; coluna < mColunas; coluna++) {
            int iAux = 0;
            for (int i = 0; i < mLinhas; i++) {
                if (i != 0) {
                    int jAux = 0;
                    for (int j = 0; j < mColunas; j++) {
                        if (j != coluna) {
                            MatAux.mData[iAux][jAux] = mData[i][j];
                            jAux++;
                        }
                    }
                    iAux++;    
                }
            }         
            soma += (-1.0)*pow(-1.0, coluna)*mData[0][coluna]*MatAux.CalculaDeterminante();    
        }
        determinante = soma;
    }    
    return determinante;  
}

//Sobrescreve o método de colchetes. Desse forma é possível acessar o ponteiro que armazena a linha no índice dado (index_linha). Como ponteiros já suportam nativamente o operador de conchetes, para acessar um determinado elemento na possivel i,j, sendo i o numero da linha e j o número da coluna, basta escrever A[i][j]. Note que o acesso ao elemento é baseado no 0, ou seja, o index do primeiro elemento será 0, e o índice do ultimo será N-1, tal que N é o número de linhas.
double* Matriz::operator[](int index_linha) {
    assert(index_linha > -1);
    assert(index_linha < mLinhas);
    return mData[index_linha];
}

//Sobrescreve o operador parenteses. Funciona de maneira semelhante ao operador colchetes, mas nesse caso, o indice do primeiro elemento é 1, e do último elemento é N, tal que N é o numero de colunas ou linhas. É utilizado de maneira semelhante a de um função, de forma que o elemento na posição i,j de uma matriz M é dado por M(i,j).
double& Matriz::operator()(int index_linha, int index_coluna) {
    assert(index_coluna > 0);
    assert(index_coluna < mColunas+1);
    return mData[index_linha][index_coluna];
}

//Sobrescreve o operador de atribuição. As informações de uma matriz é passado para outra desde que elas possuam o memso tamanho.
Matriz& Matriz::operator=(const Matriz& outraMatriz) {
    assert(mLinhas == outraMatriz.mLinhas);
    assert(mColunas == outraMatriz.mColunas);
    for (int i = 0; i < mLinhas; i++)
    {
        for (int j = 0; j < mColunas; j++)
        {
            mData[i][j] = outraMatriz.mData[i][j];
        }
    }
}

//Sobrescreve o operador de atribuição junto adição (+=). As informações de duas matrizes A e B são somadas e atribuidas a matriz A, desde que possuam o mesmo tamanho.
Matriz& Matriz::operator+=(const Matriz& outraMatriz) {
    assert(mLinhas == outraMatriz.mLinhas);
    assert(mColunas == outraMatriz.mColunas);
    for (int i = 0; i < mLinhas; i++)
    {
        for (int j = 0; j < mColunas; j++)
        {
            mData[i][j] += outraMatriz.mData[i][j];
        }            
    }
}

//Sobrescreve o operador de atribuição junto subtração (-=). As informações de duas matrizes A e B são somadas e atribuidas a matriz A, desde que possuam o mesmo tamanho.
Matriz& Matriz::operator-=(const Matriz& outraMatriz) {
    assert(mLinhas == outraMatriz.mLinhas);
    assert(mColunas == outraMatriz.mColunas);
    for (int i = 0; i < mLinhas; i++)
    {
        for (int j = 0; j < mColunas; j++)
        {
            mData[i][j] -= outraMatriz.mData[i][j];
        }            
    }
}

//Sobrescreve o operador de atribuição junto multiplicação (-=). As informações de duas matrizes A e B são multiplicadas e atribuidas a matriz A, desde que possuam o mesmo tamanho.
Matriz& Matriz::operator*=(const Matriz& outraMatriz) {
    assert(mColunas == outraMatriz.mLinhas);
    double soma;
    for (int k = 0; k < outraMatriz.mColunas; k++) {
        for (int i = 0; i < mLinhas; i++) {
            soma = 0.0;
            for (int j = 0; j < mColunas; j++) {
                soma += mData[i][j] * outraMatriz.mData[j][k];
            }
            mData[i][k] = soma;
        }        
    }
}

//Realiza a soma de duas matrizes de mesmo tamanho e retorna uma matriz com mesmo tamanho das anteriores.
Matriz Matriz::operator+(const Matriz& outraMatriz) {
    assert(mLinhas == outraMatriz.mLinhas);
    assert(mColunas == outraMatriz.mColunas);
    Matriz Saida(mLinhas, mColunas);
    for (int i = 0; i < mLinhas; i++)
    {
        for (int j = 0; j < mColunas; j++)
        {
            Saida[i][j] = mData[i][j] + outraMatriz.mData[i][j];
        }            
    }
    return Saida;
}

//Realiza a subtração de duas matrizes de mesmo tamanho e retorna uma matriz com mesmo tamanho das anteriores.
Matriz Matriz::operator-(const Matriz& outraMatriz) {
    assert(mLinhas == outraMatriz.mLinhas);
    assert(mColunas == outraMatriz.mColunas);
    Matriz Saida(mLinhas, mColunas);
    for (int i = 0; i < mLinhas; i++)
    {
        for (int j = 0; j < mColunas; j++)
        {
            Saida[i][j] = mData[i][j] - outraMatriz.mData[i][j];
        }            
    }
    return Saida;
}

//Realiza a multiplicação de um escalar n por uma matrix A e retorna uma matriz de saida com as mesma dimensões da matriz A.
Matriz Matriz::operator*(double n) {
    Matriz Saida(mLinhas, mColunas);
    for (int i = 0; i < mLinhas; i++)
    {
        for (int j = 0; j < mColunas; j++)
        {
            Saida[i][j] = mData[i][j] * n;
        }
    }
    return Saida;
}

//Realiza a multiplição de uma matriz A por uma outra matriz B e retorna uma matriz de saida com o número de linhas da matriz A e de colunas da matriz B. A operação só é realizada se o número de colunas de A for igual ao número de colunas de B.
Matriz Matriz::operator*(const Matriz& outraMatriz) {
    assert(mColunas == outraMatriz.mLinhas);
    Matriz Saida(mLinhas, outraMatriz.mColunas);
    double soma;
    for (int k = 0; k < outraMatriz.mColunas; k++) {
        for (int i = 0; i < mLinhas; i++) {
            soma = 0.0;
            for (int j = 0; j < mColunas; j++) {
                soma += mData[i][j] * outraMatriz.mData[j][k];
            }
            Saida[i][k] = soma;
        }        
    }
    return Saida;
}

//Sobrescreve o operador << para retornar na tela as informações contidas e, uma matriz A.
std::ostream& operator<<(std::ostream& saida, const Matriz& A) {
    for (int i = 0; i < A.mLinhas; i++)
    {
        for (int j = 0; j < A.mColunas; j++)
        {
            saida << A.mData[i][j] << "\t";
        }
        saida << std::endl;
    }
}

I wrote the following file to test the method CalculaDeterminante():

#include "Matriz.hpp"
#include <iostream>

int main(int argc, char const *argv[])
{
    Matriz C(3,3);
    for (int i = 0; i < C.GetNumLinhas(); i++)
    {
        for (int j = 0; j < C.GetNumColunas(); j++)
        {
            if (i%2 == 0) {
                C[i][j] = i+j+8;
            } else {
                C[i][j] = i+j*3;
            }
        }
    }
    std::cout << C << std::endl;
    std::cout << std::endl;
    std::cout << C.CalculaDeterminante() << std::endl;

    return 0;
}

The function even returns the correct value at the end of the calculations, but I would like to better understand why the error message.

Edit #1

Below the header file Matriz.hpp

#pragma once
#include <iostream>

class Matriz {

private:
    double** mData;
    int mColunas;
    int mLinhas;

public:
    Matriz();
    Matriz(int n_elementos);
    Matriz(int n_linhas, int n_colunas);
    ~Matriz();

    int GetNumLinhas() const;
    int GetNumColunas() const;

    double CalculaDeterminante();

    double* operator[](int index_linha);
    double& operator()(int index_linha, int index_coluna);

    Matriz& operator=(const Matriz& outraMatriz);
    Matriz& operator+=(const Matriz& outraMatriz);
    Matriz& operator-=(const Matriz& outraMatriz);
    Matriz& operator*=(const Matriz& outraMatriz);

    Matriz operator+(const Matriz& outraMatriz);
    Matriz operator-(const Matriz& outraMatriz);

    Matriz operator*(double n);
    Matriz operator*(const Matriz& outraMatriz);

    friend std::ostream& operator<<(std::ostream& saida, const Matriz& A);
};

Edit #2 In fact, when running the test file as previously written, the problem does not appear. But when I create and destroy a new matrix above the C Matrix declaration, as shown in the code below, that’s when the problem happens.

#include "Matriz.hpp"
#include <iostream>

int main(int argc, char const *argv[])
{
    Matriz C(3,3);
    for (int i = 0; i < C.GetNumLinhas(); i++)
    {
        for (int j = 0; j < C.GetNumColunas(); j++)
        {
            if (i%2 == 0) {
                C[i][j] = i+j+8;
            } else {
                C[i][j] = i+j*3;
            }
        }
    }
    std::cout << C << std::endl;
    std::cout << std::endl;
    std::cout << C.CalculaDeterminante() << std::endl;

    return 0;
}
  • 1

    The error means you are doing a free duplicate, and it is easy to analyze with a tool like Valgrind. If you put the Matrix.hpp header that is missing in the question it is even easier to run the code and tell where the problem is.

  • I added the file. I’ll take a look at this Valgrind too. Thank you very much!

1 answer

3


As I indicated in comment the problem indicated:

double free or corruption (Oct)

It means you either made one free in duplicate, which is wrong, or there was corruption in the heap. In both cases you can run a tool like the Valgrind that analyzes the memory and generally finds the error easily.

Observe the Valgrind result in your program:

==16144== Invalid read of size 8
==16144==    at 0x4F48B36: std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==16144==    by 0x400CFA: main (main.cpp:18)
==16144==  Address 0x3 is not stack'd, malloc'd or (recently) free'd
==16144== 
==16144== 
==16144== Process terminating with default action of signal 11 (SIGSEGV)
==16144==  Access not within mapped region at address 0x3
==16144==    at 0x4F48B36: std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==16144==    by 0x400CFA: main (main.cpp:18)
==16144==  If you believe this happened as a result of a stack
==16144==  overflow in your program's main thread (unlikely but
==16144==  possible), you can try to increase the size of the
==16144==  main thread stack using the --main-stacksize= flag.
==16144==  The main thread stack size used in this run was 8388608.
==16144== 
==16144== HEAP SUMMARY:
==16144==     in use at exit: 72,800 bytes in 5 blocks
==16144==   total heap usage: 6 allocs, 1 frees, 73,824 bytes allocated
==16144== 
==16144== LEAK SUMMARY:
==16144==    definitely lost: 0 bytes in 0 blocks
==16144==    indirectly lost: 0 bytes in 0 blocks
==16144==      possibly lost: 0 bytes in 0 blocks
==16144==    still reachable: 72,800 bytes in 5 blocks
==16144==         suppressed: 0 bytes in 0 blocks
==16144== Rerun with --leak-check=full to see details of leaked memory
==16144== 
==16144== For counts of detected and suppressed errors, rerun with: -v
==16144== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)

The analysis shows you a lot of information, but even for those who are not in this kind of report, you can see right away that there was a mistake:

==16144== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

And the mistake came up on line 18:

==16144==    by 0x400CFA: main (main.cpp:18)

And actually the error was an invalid read, so on a memory block that is not assigned:

==16144== Invalid read of size 8

On line 18 you have the matrix writing by cout:

std::cout << C << std::endl;

Which calls it the operator’s Overload <<:

std::ostream& operator<<(std::ostream& saida, const Matriz& A) {
    for (int i = 0; i < A.mLinhas; i++)
    {
        for (int j = 0; j < A.mColunas; j++)
        {
            saida << A.mData[i][j] << "\t";
        }
        saida << std::endl;
    }
}

Notice there’s a comeback like std::ostream&. You are not returning anything and the error is precisely there. You have to return the output stream you received in the first parameter, thus adding in the last line:

return saida;

This fixes the problem and solves the Segmentation fault seeing:

==16190== 
==16190== HEAP SUMMARY:
==16190==     in use at exit: 72,704 bytes in 1 blocks
==16190==   total heap usage: 15 allocs, 14 frees, 73,920 bytes allocated
==16190== 
==16190== LEAK SUMMARY:
==16190==    definitely lost: 0 bytes in 0 blocks
==16190==    indirectly lost: 0 bytes in 0 blocks
==16190==      possibly lost: 0 bytes in 0 blocks
==16190==    still reachable: 72,704 bytes in 1 blocks
==16190==         suppressed: 0 bytes in 0 blocks
==16190== Rerun with --leak-check=full to see details of leaked memory
==16190== 
==16190== For counts of detected and suppressed errors, rerun with: -v
==16190== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

To complete, in the overloads of other operators have the same problem. Taking for example the operator +=:

Matriz& Matriz::operator+=(const Matriz& outraMatriz) {
    assert(mLinhas == outraMatriz.mLinhas);
    assert(mColunas == outraMatriz.mColunas);
    for (int i = 0; i < mLinhas; i++)
    {
        for (int j = 0; j < mColunas; j++)
        {
            mData[i][j] += outraMatriz.mData[i][j];
        }
    }
}

The type of return is Matriz& which is right, but again does not return anything that is wrong. Correct is to return the following:

return *this;

You need to apply the correction to all of them.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.