Valgrind 使用 shared_ptr c++11 指示内存泄漏
Valgrind indicates memory leak with shared_ptr c++11
我已经使用 C++11 智能指针实现了链表。此实现使用 shared_ptr 存储内部数据结构,用于实现它的隐式共享。我在下面提供源代码的相关部分:
namespace Algos {
template <typename T>
struct LinkedListData{
struct Node {
std::unique_ptr<Node> next;
Node *prev = nullptr;
T data;
};
std::unique_ptr<Node> root;
Node *last = nullptr;
int size = 0;
LinkedListData() {
root = std::make_unique<Node>();
root->prev = nullptr; //last virtual element
root->next = std::make_unique<Node>();
last = root->next.get();
last->prev = root.get();
last->next = nullptr;
}
//deferr pointers manually to avoid Whosebug due to
//recursion explosion
static void cleanup(LinkedListData<T> *data) {
#ifdef DEBUG_TXT
int nodeCount=0;
#endif
Node *n = data->last;
if(n==nullptr) { return; }
while(n) {
#ifdef DEBUG_TXT
if(n->next.get())
std::cout << "Release {n->next()} [" << ++nodeCount <<
"]: "<< n->next.get() << std::endl;
#endif
n->next.release();
ALGO_ASSERT(n->next.get() == nullptr, "Node reference not deferred");
n = n->prev;
}
data->size = 0;
#ifdef DEBUG_TXT
std::cout << "Release {Root} [" << ++nodeCount << "]: "<< data->root.get() << std::endl;
#endif
data->root.release();
}
};
template <class T>
class LinkedList {
typedef typename LinkedListData<T>::Node node_type;
std::shared_ptr<LinkedListData<T> > d;
public:
LinkedList() : d(new LinkedListData<T>(), LinkedListData<T>::cleanup){}
/* Code omitted .... */
};
}
由于使用 new,以下代码在 shared_pointer 构造函数中触发 valgrind 的内存泄漏错误:
#include <iostream>
#include <string>
#include <unistd.h>
// #define DEBUG_TXT
#include "global/assert.h"
#include "linked_list/linkedlist.h"
#define TEST_SIZE 5000
struct DataTest {
int integer;
bool boolean;
std::string txt;
DataTest& operator=(const DataTest& other) {
integer = other.integer;
boolean = other.boolean;
txt = other.txt;
return (*this);
}
bool operator==(const DataTest& other) const {
return (
integer == other.integer &&
boolean == other.boolean &&
txt == other.txt
);
}
};
struct Data {
DataTest data[TEST_SIZE];
const int n = TEST_SIZE;
static void initDataSample(Data &d) {
for(int i=0; i<d.n; i++) {
d.data[i].integer = i;
d.data[i].boolean = (i%2 == 0);
d.data[i].txt = "abc";
}
}
};
void appendElements(Algos::LinkedList<DataTest> &l, const Data& d){
for(int i=0; i<d.n; i++) {
l.append(d.data[i]);
}
}
void prependElements(Algos::LinkedList<DataTest> &l, const Data& d) {
for(int i=d.n-1; i>=0; i--) {
l.prepend(d.data[i]);
}
}
int main(int argv, char* argc[]) {
Data d;
Data::initDataSample(d);
Algos::LinkedList<DataTest> l1;
{
Algos::LinkedList<DataTest> l2;
l1 = l2;
}
sleep(2);
appendElements(l1, d);
int removeSize = l1.size()/2;
for(int i=0; i<removeSize; i++)
l1.takeFirst();
prependElements(l1, d);
removeSize = l1.size()/2;
for(int i=0; i<removeSize; i++)
l1.takeLast();
return 0;
}
这是我在 valgrind 控制台中收到的消息:
> ==8897== HEAP SUMMARY:
==8897== in use at exit: 282,976 bytes in 3,757 blocks
==8897== total heap usage: 10,009 allocs, 6,252 frees, 633,040 bytes allocated
==8897==
==8897== 136 (24 direct, 112 indirect) bytes in 1 blocks are definitely lost in loss record 5 of 8
==8897== at 0x4C2E216: operator new(unsigned long) (vg_replace_malloc.c:334)
==8897== by 0x407C1C: Algos::LinkedList<DataTest>::LinkedList() (linkedlist.h:74)
==8897== by 0x4074B4: main (insert_delete_rounds.cpp:63)
==8897==
==8897== 210,136 (24 direct, 210,112 indirect) bytes in 1 blocks are definitely lost in loss record 8 of 8
==8897== at 0x4C2E216: operator new(unsigned long) (vg_replace_malloc.c:334)
==8897== by 0x407C1C: Algos::LinkedList<DataTest>::LinkedList() (linkedlist.h:74)
==8897== by 0x4074C3: main (insert_delete_rounds.cpp:65)
==8897==
==8897== LEAK SUMMARY:
==8897== definitely lost: 48 bytes in 2 blocks
==8897== indirectly lost: 210,224 bytes in 3,754 blocks
==8897== possibly lost: 0 bytes in 0 blocks
==8897== still reachable: 72,704 bytes in 1 blocks
==8897== suppressed: 0 bytes in 0 blocks
==8897== Reachable blocks (those to which a pointer was found) are not shown.
==8897== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==8897==
==8897== For counts of detected and suppressed errors, rerun with: -v
==8897== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
据我所知,没有其他方法可以在不使用 [= C++11 中的 29=]new。在阅读了 Whosebug 上的文档和不同线程后,我注意到 std::make_shared() 不支持传递自定义删除。所以,我的问题是:这个内存泄漏警告是否合法?如果是,是否可以避免?
开发工具设置:
- gcc: 5.4.0
- Valgrind:3.13.0
- OS: Linux Ubuntu 16.04
回答您的问题:
您使用 allocate_shared 允许使用自定义内存分配器。签名是相同的,除了它采用对分配器的 const 引用作为第一个参数。
尽管您可以清理 LinkedListData
的内部结构,但您永远不会 delete
在 cleanup
方法中分配指针。 cleanup
应该从 LinkedListData
析构函数中调用,您应该使用常规删除器(如果使用自定义分配器,则使用自定义删除器)在 LinkedListData
指针上调用 delete。
简而言之,这一行总是会导致内存泄漏:
LinkedList() : d(new LinkedListData<T>(), LinkedListData<T>::cleanup){}
您创建了一个 new LinkedListData<T>()
,但您没有在 cleanup
中调用 delete
。
由于您在内部使用智能指针,因此您不应将 LinkedListData::cleanup
指定为自定义删除器,而应使用默认删除器。如果您试图坚持使用智能指针,那么如果您没有显式调用 delete
,那么避免使用 new
调用可能是个好主意。因此,d
成员可以只用 d(std::make_shared<LinkedListData<T>>())
初始化。此外,它可能可以像 LinkedListData<T> d;
一样声明,那里没有任何智能指针。
那么,你不应该在LinkedListData::cleanup
中调用release
。此方法从智能指针包装器中分离原始指针,并且不释放关联的对象。您可能需要 reset
来代替。但是再一次,这个方法根本不是必需的,因为你所有的指针都是智能的,相关数据应该在调用父析构函数时自动清理。
我已经使用 C++11 智能指针实现了链表。此实现使用 shared_ptr 存储内部数据结构,用于实现它的隐式共享。我在下面提供源代码的相关部分:
namespace Algos {
template <typename T>
struct LinkedListData{
struct Node {
std::unique_ptr<Node> next;
Node *prev = nullptr;
T data;
};
std::unique_ptr<Node> root;
Node *last = nullptr;
int size = 0;
LinkedListData() {
root = std::make_unique<Node>();
root->prev = nullptr; //last virtual element
root->next = std::make_unique<Node>();
last = root->next.get();
last->prev = root.get();
last->next = nullptr;
}
//deferr pointers manually to avoid Whosebug due to
//recursion explosion
static void cleanup(LinkedListData<T> *data) {
#ifdef DEBUG_TXT
int nodeCount=0;
#endif
Node *n = data->last;
if(n==nullptr) { return; }
while(n) {
#ifdef DEBUG_TXT
if(n->next.get())
std::cout << "Release {n->next()} [" << ++nodeCount <<
"]: "<< n->next.get() << std::endl;
#endif
n->next.release();
ALGO_ASSERT(n->next.get() == nullptr, "Node reference not deferred");
n = n->prev;
}
data->size = 0;
#ifdef DEBUG_TXT
std::cout << "Release {Root} [" << ++nodeCount << "]: "<< data->root.get() << std::endl;
#endif
data->root.release();
}
};
template <class T>
class LinkedList {
typedef typename LinkedListData<T>::Node node_type;
std::shared_ptr<LinkedListData<T> > d;
public:
LinkedList() : d(new LinkedListData<T>(), LinkedListData<T>::cleanup){}
/* Code omitted .... */
};
}
由于使用 new,以下代码在 shared_pointer 构造函数中触发 valgrind 的内存泄漏错误:
#include <iostream>
#include <string>
#include <unistd.h>
// #define DEBUG_TXT
#include "global/assert.h"
#include "linked_list/linkedlist.h"
#define TEST_SIZE 5000
struct DataTest {
int integer;
bool boolean;
std::string txt;
DataTest& operator=(const DataTest& other) {
integer = other.integer;
boolean = other.boolean;
txt = other.txt;
return (*this);
}
bool operator==(const DataTest& other) const {
return (
integer == other.integer &&
boolean == other.boolean &&
txt == other.txt
);
}
};
struct Data {
DataTest data[TEST_SIZE];
const int n = TEST_SIZE;
static void initDataSample(Data &d) {
for(int i=0; i<d.n; i++) {
d.data[i].integer = i;
d.data[i].boolean = (i%2 == 0);
d.data[i].txt = "abc";
}
}
};
void appendElements(Algos::LinkedList<DataTest> &l, const Data& d){
for(int i=0; i<d.n; i++) {
l.append(d.data[i]);
}
}
void prependElements(Algos::LinkedList<DataTest> &l, const Data& d) {
for(int i=d.n-1; i>=0; i--) {
l.prepend(d.data[i]);
}
}
int main(int argv, char* argc[]) {
Data d;
Data::initDataSample(d);
Algos::LinkedList<DataTest> l1;
{
Algos::LinkedList<DataTest> l2;
l1 = l2;
}
sleep(2);
appendElements(l1, d);
int removeSize = l1.size()/2;
for(int i=0; i<removeSize; i++)
l1.takeFirst();
prependElements(l1, d);
removeSize = l1.size()/2;
for(int i=0; i<removeSize; i++)
l1.takeLast();
return 0;
}
这是我在 valgrind 控制台中收到的消息:
> ==8897== HEAP SUMMARY:
==8897== in use at exit: 282,976 bytes in 3,757 blocks
==8897== total heap usage: 10,009 allocs, 6,252 frees, 633,040 bytes allocated
==8897==
==8897== 136 (24 direct, 112 indirect) bytes in 1 blocks are definitely lost in loss record 5 of 8
==8897== at 0x4C2E216: operator new(unsigned long) (vg_replace_malloc.c:334)
==8897== by 0x407C1C: Algos::LinkedList<DataTest>::LinkedList() (linkedlist.h:74)
==8897== by 0x4074B4: main (insert_delete_rounds.cpp:63)
==8897==
==8897== 210,136 (24 direct, 210,112 indirect) bytes in 1 blocks are definitely lost in loss record 8 of 8
==8897== at 0x4C2E216: operator new(unsigned long) (vg_replace_malloc.c:334)
==8897== by 0x407C1C: Algos::LinkedList<DataTest>::LinkedList() (linkedlist.h:74)
==8897== by 0x4074C3: main (insert_delete_rounds.cpp:65)
==8897==
==8897== LEAK SUMMARY:
==8897== definitely lost: 48 bytes in 2 blocks
==8897== indirectly lost: 210,224 bytes in 3,754 blocks
==8897== possibly lost: 0 bytes in 0 blocks
==8897== still reachable: 72,704 bytes in 1 blocks
==8897== suppressed: 0 bytes in 0 blocks
==8897== Reachable blocks (those to which a pointer was found) are not shown.
==8897== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==8897==
==8897== For counts of detected and suppressed errors, rerun with: -v
==8897== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
据我所知,没有其他方法可以在不使用 [= C++11 中的 29=]new。在阅读了 Whosebug 上的文档和不同线程后,我注意到 std::make_shared() 不支持传递自定义删除。所以,我的问题是:这个内存泄漏警告是否合法?如果是,是否可以避免?
开发工具设置:
- gcc: 5.4.0
- Valgrind:3.13.0
- OS: Linux Ubuntu 16.04
回答您的问题:
您使用 allocate_shared 允许使用自定义内存分配器。签名是相同的,除了它采用对分配器的 const 引用作为第一个参数。
尽管您可以清理
LinkedListData
的内部结构,但您永远不会delete
在cleanup
方法中分配指针。cleanup
应该从LinkedListData
析构函数中调用,您应该使用常规删除器(如果使用自定义分配器,则使用自定义删除器)在LinkedListData
指针上调用 delete。
简而言之,这一行总是会导致内存泄漏:
LinkedList() : d(new LinkedListData<T>(), LinkedListData<T>::cleanup){}
您创建了一个 new LinkedListData<T>()
,但您没有在 cleanup
中调用 delete
。
由于您在内部使用智能指针,因此您不应将 LinkedListData::cleanup
指定为自定义删除器,而应使用默认删除器。如果您试图坚持使用智能指针,那么如果您没有显式调用 delete
,那么避免使用 new
调用可能是个好主意。因此,d
成员可以只用 d(std::make_shared<LinkedListData<T>>())
初始化。此外,它可能可以像 LinkedListData<T> d;
一样声明,那里没有任何智能指针。
那么,你不应该在LinkedListData::cleanup
中调用release
。此方法从智能指针包装器中分离原始指针,并且不释放关联的对象。您可能需要 reset
来代替。但是再一次,这个方法根本不是必需的,因为你所有的指针都是智能的,相关数据应该在调用父析构函数时自动清理。