如何确认您没有内存泄漏?
How to confirm you are not having memory leaks?
所以..我正在尝试编写我的第一个析构函数。我有某事,但我不知道它是否真的在做它应该做的事情。
我有一个线性链表,其中每个节点都有 3 个动态分配的 char 数组和一个下一个指针 (heads
) 指向另一个也有 1 个动态 char 数组的链表。换句话说,数据结构是链表的链表。这是我写的:
nodea * tempa = heada;//create a new node to
nodesongs *temps=heada->heads;
while(heada)// while the list is not empty
{
tempa= heada ->nexta;// traverse the list
delete [] heada->dataa.name ;
delete [] heada->dataa.story ;
delete [] heada->dataa.description ;
while(heada->heads)
{
temps = heada ->heads->nexts;
delete[] heada->heads->datas.title;
delete heada->heads;
heada->heads = temps;
}
delete heada;
heada = tempa;
}
如果您能看一下,我将不胜感激,并且我想知道人们是否有一种通用的方法来测试他们的代码是否存在内存泄漏?
虽然关于 'use smart pointers' 的评论是正确的,但它们无法帮助您找到所需的内容。
由于它依赖于平台,因此您正在寻找的工具是一种称为内存分析器的工具。
有一些可用,有些是付费的,有些不是,而且大部分都是特定于平台的。如果您使用 Linux,我可以推荐的一个是 valgrind。
How to confirm you are not having memory leaks?
C++ 程序没有标准的方法来检查其自身状态并发现内存泄漏。
然而,有一些编程工具可以检测特定执行是否已泄漏内存。这些可以告诉您泄露了什么,但这不一定能帮助您理解为什么它被泄露。
你可以做的是分析你写的程序,并从逻辑上推断是否有任何手动分配的内存会泄漏。这通常很难做到(这就是编译器无法为您完成的原因),但如果您遵循最佳实践,它会变得易于管理:
- 切勿手动管理内存(99.99% 的可能性您不需要)
- 永远不要调用
new
,也不要调用 malloc
- 总是在收到智能指针后立即将拥有的指针包装在智能指针中
- 始终阅读您使用的库的文档。如果一个函数给你一个裸指针,仔细检查你是否负责处理它;如果是,请参阅 2。
- 这在处理具有 C API 的库时很典型。有时也会使用旧的或设计糟糕的 C++ API。
- 如果您手动管理内存(但不要这样做,因为您不需要)
- 只在 RAII 时尚中这样做
- 拥有指针应该是 class 的私有成员。这种封装将你必须分析的范围从整个程序缩小到这个 class 的成员函数。
- 永远不要 return 引用或指向该成员,仅复制(甚至可能不是)。返回一个引用会破坏封装(只有非 const 引用,但是在 returning a pointer by const reference 中没有 point)。
- Class 不变:指针必须始终指向 null 或有效的动态分配对象; class 的任何其他实例都不能指向该对象。
- 如果允许成员不指向有效的动态分配对象(或 null),则处于这种状态的对象的析构函数将具有未定义的行为。
- 如果允许多个对象拥有同一个指针,那么销毁一个对象会导致另一个对象违反关于指向有效对象的不变量。
- 隐式 copy/move 构造函数和赋值运算符执行浅拷贝,这会导致违反这些不变量。因此我们必须遵循rule of 3 (or 5)
- class 的析构函数必须删除指针
- 任何一次该成员被分配,要么删除旧值,要么"transfer"它的所有权给另一个对象(除非指针有一个未指定的值,它只能在构造函数中,因为 5 .).这些分配是您最有可能意外泄漏内存的地方。
- 每个 class 最多只能拥有 1 个裸指针。如有必要,使用嵌套的 classes。这个建议是为了让保证exception safety成为可能/更容易。缺乏异常安全性是更隐蔽的内存泄漏方式。
关于您的代码,让我们假设您必须手动管理内存并学习如何操作,因此我的建议 1 和 2 不可行:
delete [] heada->dataa.name ;
delete [] heada->dataa.story ;
错误1:不删除其他对象拥有的指针。这违反了我的建议 6。改为在该对象的析构函数中删除。
错误2:在容器对象的生命周期内(*heada
),不要让指针指向一个被删除的对象这是错误1的结果,违反了我的建议5。
错误 3:不要在单个对象中有多个裸拥有指针。这违反了我的建议 8.
没有明确的方法(参见 Rice's theorem),但是:
学习C++11标准,更喜欢标准containers (such as std::map
s), standard types (such as std::string
s) and smart pointers (e.g. std::shared_ptr
s)
注意并始终遵循 rule of five
使用现有的调试工具,例如valgrind and GCC instrumentation options (notably its address sanitizer).
在某些情况下,使用一些(或思考)garbage collector can be helpful. Sometimes coding your own marking GC is easy and worthwhile. See also Boehm's GC。顺便说一句,有时 C++ 并不是这项工作的最佳语言。
评论扩展为答案:
您应该努力编写内存泄漏 不可能 的代码,并且通过一定的纪律这是可以合理实现的。如果你不能使用namespace std
中的东西,那就写你自己的复制品。
例如唯一使用 new
:
的地方
template<typename T>
struct pointer
{
template<typename ... TArgs>
pointer(TArgs&&... targs) : p(new T(targs...)) {}
pointer(const pointer & other) = delete;
pointer& operator=(const pointer & other) = delete;
pointer(pointer && other) : p(other.p) { other.p = nullptr; }
pointer& operator=(pointer && other) { std::swap(this->p, other.p); }
~pointer { delete p; }
T& operator*() { return *p; }
T* operator->() { return *p; }
private:
T * p = nullptr;
}
所以..我正在尝试编写我的第一个析构函数。我有某事,但我不知道它是否真的在做它应该做的事情。
我有一个线性链表,其中每个节点都有 3 个动态分配的 char 数组和一个下一个指针 (heads
) 指向另一个也有 1 个动态 char 数组的链表。换句话说,数据结构是链表的链表。这是我写的:
nodea * tempa = heada;//create a new node to
nodesongs *temps=heada->heads;
while(heada)// while the list is not empty
{
tempa= heada ->nexta;// traverse the list
delete [] heada->dataa.name ;
delete [] heada->dataa.story ;
delete [] heada->dataa.description ;
while(heada->heads)
{
temps = heada ->heads->nexts;
delete[] heada->heads->datas.title;
delete heada->heads;
heada->heads = temps;
}
delete heada;
heada = tempa;
}
如果您能看一下,我将不胜感激,并且我想知道人们是否有一种通用的方法来测试他们的代码是否存在内存泄漏?
虽然关于 'use smart pointers' 的评论是正确的,但它们无法帮助您找到所需的内容。
由于它依赖于平台,因此您正在寻找的工具是一种称为内存分析器的工具。
有一些可用,有些是付费的,有些不是,而且大部分都是特定于平台的。如果您使用 Linux,我可以推荐的一个是 valgrind。
How to confirm you are not having memory leaks?
C++ 程序没有标准的方法来检查其自身状态并发现内存泄漏。
然而,有一些编程工具可以检测特定执行是否已泄漏内存。这些可以告诉您泄露了什么,但这不一定能帮助您理解为什么它被泄露。
你可以做的是分析你写的程序,并从逻辑上推断是否有任何手动分配的内存会泄漏。这通常很难做到(这就是编译器无法为您完成的原因),但如果您遵循最佳实践,它会变得易于管理:
- 切勿手动管理内存(99.99% 的可能性您不需要)
- 永远不要调用
new
,也不要调用malloc
- 总是在收到智能指针后立即将拥有的指针包装在智能指针中
- 始终阅读您使用的库的文档。如果一个函数给你一个裸指针,仔细检查你是否负责处理它;如果是,请参阅 2。
- 这在处理具有 C API 的库时很典型。有时也会使用旧的或设计糟糕的 C++ API。
- 永远不要调用
- 如果您手动管理内存(但不要这样做,因为您不需要)
- 只在 RAII 时尚中这样做
- 拥有指针应该是 class 的私有成员。这种封装将你必须分析的范围从整个程序缩小到这个 class 的成员函数。
- 永远不要 return 引用或指向该成员,仅复制(甚至可能不是)。返回一个引用会破坏封装(只有非 const 引用,但是在 returning a pointer by const reference 中没有 point)。
- Class 不变:指针必须始终指向 null 或有效的动态分配对象; class 的任何其他实例都不能指向该对象。
- 如果允许成员不指向有效的动态分配对象(或 null),则处于这种状态的对象的析构函数将具有未定义的行为。
- 如果允许多个对象拥有同一个指针,那么销毁一个对象会导致另一个对象违反关于指向有效对象的不变量。
- 隐式 copy/move 构造函数和赋值运算符执行浅拷贝,这会导致违反这些不变量。因此我们必须遵循rule of 3 (or 5)
- class 的析构函数必须删除指针
- 任何一次该成员被分配,要么删除旧值,要么"transfer"它的所有权给另一个对象(除非指针有一个未指定的值,它只能在构造函数中,因为 5 .).这些分配是您最有可能意外泄漏内存的地方。
- 每个 class 最多只能拥有 1 个裸指针。如有必要,使用嵌套的 classes。这个建议是为了让保证exception safety成为可能/更容易。缺乏异常安全性是更隐蔽的内存泄漏方式。
- 拥有指针应该是 class 的私有成员。这种封装将你必须分析的范围从整个程序缩小到这个 class 的成员函数。
- 只在 RAII 时尚中这样做
关于您的代码,让我们假设您必须手动管理内存并学习如何操作,因此我的建议 1 和 2 不可行:
delete [] heada->dataa.name ; delete [] heada->dataa.story ;
错误1:不删除其他对象拥有的指针。这违反了我的建议 6。改为在该对象的析构函数中删除。
错误2:在容器对象的生命周期内(*heada
),不要让指针指向一个被删除的对象这是错误1的结果,违反了我的建议5。
错误 3:不要在单个对象中有多个裸拥有指针。这违反了我的建议 8.
没有明确的方法(参见 Rice's theorem),但是:
学习C++11标准,更喜欢标准containers (such as
std::map
s), standard types (such asstd::string
s) and smart pointers (e.g.std::shared_ptr
s)注意并始终遵循 rule of five
使用现有的调试工具,例如valgrind and GCC instrumentation options (notably its address sanitizer).
在某些情况下,使用一些(或思考)garbage collector can be helpful. Sometimes coding your own marking GC is easy and worthwhile. See also Boehm's GC。顺便说一句,有时 C++ 并不是这项工作的最佳语言。
评论扩展为答案:
您应该努力编写内存泄漏 不可能 的代码,并且通过一定的纪律这是可以合理实现的。如果你不能使用namespace std
中的东西,那就写你自己的复制品。
例如唯一使用 new
:
template<typename T>
struct pointer
{
template<typename ... TArgs>
pointer(TArgs&&... targs) : p(new T(targs...)) {}
pointer(const pointer & other) = delete;
pointer& operator=(const pointer & other) = delete;
pointer(pointer && other) : p(other.p) { other.p = nullptr; }
pointer& operator=(pointer && other) { std::swap(this->p, other.p); }
~pointer { delete p; }
T& operator*() { return *p; }
T* operator->() { return *p; }
private:
T * p = nullptr;
}