Valgrind identifica vazamentos de memória ao usar membro do tipo string (compilando com nvcc)
-
21-12-2019 - |
Pergunta
Não tenho certeza se é um erro ou não, mas quando uso membros do tipo string dentro de estruturas ou classes, valgrind identifica vazamentos de memória.Tentei construir um código simples baseado na minha própria aplicação, me desculpe se ainda é grande...
// ====================================================================
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
// ====================================================================
string int2str(const int &i) {
return static_cast<ostringstream*>(
&(ostringstream() << i))->str();
}
// ====================================================================
class P;
// ====================================================================
struct Node {
virtual char isType() const = 0;
};
// ====================================================================
struct X : Node {
string st;
int id;
X(const string &_st, const int &_id);
char isType() const { return 'x'; };
// Those member functions are after class P declaration:
P use_as_P();
P use_as_P(const P &arg0);
P use_as_P(const P &arg0, const P &arg1);
};
X::X(const string &_st, const int &_id) : st(_st), id(_id) { }
// ====================================================================
class P {
friend struct X;
private:
Node *node;
vector<P> children;
public:
P() : node(NULL) {};
P(const P &source);
void swap(P &other);
string print_this();
~P();
};
P::P(const P &source) {
this->children = source.children;
switch(source.node->isType()) {
case 'x':
this->node = new X(static_cast<X*>(source.node)->st,
static_cast<X*>(source.node)->id);
break;
}
}
void P::swap(P &other) {
std::swap(this->node, other.node);
std::swap(this->children, other.children);
}
string P::print_this() {
string msg = "( ";
msg += static_cast<X*>(this->node)->st;
msg += int2str(static_cast<X*>(this->node)->id);
msg += " ";
for(size_t i = 0; i < this->children.size(); i++)
msg += children.at(i).print_this();
msg += ") ";
return msg;
}
P::~P() {
if(this->node != NULL)
delete node;
this->children.clear();
}
// ====================================================================
P X::use_as_P() {
P ast_aux;
ast_aux.node = new X(this->st,this->id);
return ast_aux;
}
P X::use_as_P(const P &arg0) {
P ast_aux;
ast_aux.node = new X(this->st,this->id);
ast_aux.children.push_back(arg0);
return ast_aux;
}
P X::use_as_P(const P &arg0, const P &arg1) {
P ast_aux;
ast_aux.node = new X(this->st,this->id);
ast_aux.children.push_back(arg0);
ast_aux.children.push_back(arg1);
return ast_aux;
}
// ====================================================================
// ** MAIN **
// ====================================================================
int main(int argc, char **argv)
{
X a("how",0), b("what",1), c("why",2), d("when",3);
P testing = a.use_as_P(b.use_as_P(c.use_as_P()),d.use_as_P());
cout << testing.print_this() << endl;
return 0;
}
// ====================================================================
Compilando com:
nvcc -arch sm_20 -o LEAK_test_with_string LEAK_test_with_string.cu
E aqui vai a análise do valgrind:
==5877== Memcheck, a memory error detector
==5877== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==5877== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==5877== Command: ./LEAK_test_with_string
==5877==
( how0 ( what1 ( why2 ) ) ( when3 ) )
==5877==
==5877== HEAP SUMMARY:
==5877== in use at exit: 114 bytes in 4 blocks
==5877== total heap usage: 47 allocs, 43 frees, 3,701 bytes allocated
==5877==
==5877== 28 bytes in 1 blocks are definitely lost in loss record 1 of 4
==5877== at 0x4C2A879: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5877== by 0x5516F38: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
==5877== by 0x5518640: char* std::string::_S_construct<char const*>(char const*, char const*, std::allocator<char> const&, std::forward_iterator_tag) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
==5877== by 0x5518A57: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
==5877== by 0x403691: main (in /home/igor/projects/system_modeling/LEAK_test_with_string)
==5877== : st(_st), id(_id) {}
==5877== 28 bytes in 1 blocks are definitely lost in loss record 2 of 4
==5877== at 0x4C2A879: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5877== by 0x5516F38: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
==5877== by 0x5518640: char* std::string::_S_construct<char const*>(char const*, char const*, std::allocator<char> const&, std::forward_iterator_tag) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
==5877== by 0x5518A57: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
==5877== by 0x40375A: main (in /home/igor/projects/system_modeling/LEAK_test_with_string)
==5877==
==5877== 29 bytes in 1 blocks are definitely lost in loss record 3 of 4
==5877== at 0x4C2A879: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5877== by 0x5516F38: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
==5877== by 0x5518640: char* std::string::_S_construct<char const*>(char const*, char const*, std::allocator<char> const&, std::forward_iterator_tag) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
==5877== by 0x5518A57: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
==5877== by 0x4036F7: main (in /home/igor/projects/system_modeling/LEAK_test_with_string)
==5877==
==5877== 29 bytes in 1 blocks are definitely lost in loss record 4 of 4
==5877== at 0x4C2A879: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5877== by 0x5516F38: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
==5877== by 0x5518640: char* std::string::_S_construct<char const*>(char const*, char const*, std::allocator<char> const&, std::forward_iterator_tag) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
==5877== by 0x5518A57: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
==5877== by 0x4037BD: main (in /home/igor/projects/system_modeling/LEAK_test_with_string)
==5877==
==5877== LEAK SUMMARY:
==5877== definitely lost: 114 bytes in 4 blocks
==5877== indirectly lost: 0 bytes in 0 blocks
==5877== possibly lost: 0 bytes in 0 blocks
==5877== still reachable: 0 bytes in 0 blocks
==5877== suppressed: 0 bytes in 0 blocks
==5877==
==5877== For counts of detected and suppressed errors, rerun with: -v
==5877== ERROR SUMMARY: 4 errors from 4 contexts (suppressed: 2 from 2)
A pergunta "é um bug" surge quando eu mudo o C++ corda membro em um C simples Caracteres* membro.Basta alterar esta parte do código escrito acima:
// ====================================================================
struct X : Node {
char st[6]; // <=============== HERE!
int id;
X(const string &_st, const int &_id);
char isType() const { return 'x'; };
// Those member functions are after class P declaration:
P use_as_P();
P use_as_P(const P &arg0);
P use_as_P(const P &arg0, const P &arg1);
};
X::X(const string &_st, const int &_id) : id(_id) { // <=============== HERE!
strcpy(st, _st.c_str()); // <=============== HERE!
}
// ====================================================================
Observe que eu ainda uso corda no meu código, mas agora não há nenhum corda membro declarou.Dessa forma, valgrind não reclama mais:
==5977== Memcheck, a memory error detector
==5977== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==5977== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==5977== Command: ./LEAK_test_without_string
==5977==
( how0 ( what1 ( why2 ) ) ( when3 ) )
==5977==
==5977== HEAP SUMMARY:
==5977== in use at exit: 0 bytes in 0 blocks
==5977== total heap usage: 57 allocs, 57 frees, 3,986 bytes allocated
==5977==
==5977== All heap blocks were freed -- no leaks are possible
==5977==
==5977== For counts of detected and suppressed errors, rerun with: -v
==5977== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
Alguém tem uma ideia sobre isso?Quero dizer, isso é um bug que deveria ser relatado ou há algo que realmente estou faltando no meu código de versão de string?
Solução
Como ninguém mordeu a bala, expandirei meu comentário:Seu Node
classe não possui um destruidor virtual, isso faz com que a seguinte linha
delete node;
invocar comportamento indefinido - isso chamará apenas o destruidor para Node
, o destruidor de X
nunca é chamado.
A solução é simples, forneça um destruidor virtual para Node
:
struct Node {
virtual char isType() const = 0;
virtual ~Node() =default;
// or virtual ~Node(){} if your compiler does not support defaulted functions.
}
Agora você pode com segurança delete
ponteiros base de Node
que apontam para classes herdadas.
O motivo do vazamento de memória ao usar um std::string
é porque quando você exclui o ponteiro base node
, o destruidor de X
não é chamado e, portanto, o destruidor também não é st
.O char
a versão do array não vaza porque node
aponta para um bloco simples de memória, e X
neste caso não tem nenhum membro complexo que exija destruição, então delete node
felizmente libera toda a memória alocada para X
e seus membros.Lembre-se que isso é comportamento indefinido entretanto, o compilador pode legitimamente fazer o que quiser.
Se você tiver uma classe base com pelo menos um único virtual
função, sempre adicione um destruidor virtual.Você pode defender a omissão de um destruidor virtual se tiver certeza absoluta de que não excluirá classes herdadas usando um ponteiro para a classe base, mas é mais seguro apenas adicionar um de qualquer maneira, apenas por precaução (o sinalizador do compilador gcc -Weffc++
também lhe direi isso).