Valgrind在使用字符串类型成员(使用nvcc编译)时识别内存泄漏

Valgrind identifies memory leaks when using string-type member (compiling with nvcc)

本文关键字:编译 nvcc 识别 泄漏 内存 使用 字符串 成员 类型 Valgrind      更新时间:2023-10-16

我不确定这是错误还是不是,但是当我在结构或类中使用字符串类型成员时,valgrind识别内存泄漏。我试图建立一个简单的代码基于我自己的应用程序,我很抱歉,如果它仍然很大…

//  ====================================================================
#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;
}
//  ====================================================================
编译:

nvcc -arch sm_20 -o LEAK_test_with_string LEAK_test_with_string.cu
下面是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)

当我将c++ string成员更改为普通的C char*成员时,出现了"这是一个bug"的疑惑。只需要更改上面编写的代码的这一部分:

//  ====================================================================
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!
}
//  ====================================================================
请注意,我仍然在代码中使用string,但是现在没有声明任何string成员。这样,valgrind就不会再抱怨了:
==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)

有人对此有什么见解吗?我的意思是,这是一个应该报告的bug,还是我在字符串版本的代码中实际上缺少了什么?

既然没人敢这么做,我就扩展一下我的评论:你的Node类没有虚析构函数,这使得下面这行

delete node;

调用未定义的行为——这只会调用Node的析构函数,X的析构函数永远不会被调用。

解决方法很简单,为Node提供一个虚析构函数:

struct Node { 
    virtual char isType() const = 0;
    virtual ~Node() =default;
    // or virtual ~Node(){} if your compiler does not support defaulted functions.
}

现在可以安全地Node的基指针delete指向继承的类。

使用std::string时发生内存泄漏的原因是,当您删除基指针node时,没有调用X的析构函数,因此也没有调用st的析构函数。char数组版本不会泄漏,因为node指向一个简单的内存块,而X在这种情况下没有任何需要销毁的复杂成员,因此delete node幸运地释放了为X及其成员分配的所有内存。请记住,这是未定义的行为然而,编译器可以合法地做任何它想做的事情。

如果基类只有一个virtual函数,总是添加虚析构函数。如果您绝对确定不会使用指向基类的指针删除继承的类,则可以省略虚析构函数,但为了以防万一,添加一个虚析构函数会更安全(gcc编译器标志-Weffc++也会告诉您这一点)。