简体   繁体   English

智能指针可能发生内存泄漏

[英]Possible Memory-leaks with smart pointers

I have been around the C++ community for a while to hear that raw pointers "are evil" and that they should be avoided as much as possible. 我有一段时间在C ++社区,听到原始指针“是邪恶的”,应该尽可能避免它们。 While one of the main reasons to use smart pointers over raw pointers is to "prevent" memory leaks. 虽然在原始指针上使用智能指针的主要原因之一是“防止”内存泄漏。 So my question is: Even when using smart pointers, is it still possible to have memory leak ? 所以我的问题是:即使使用智能指针,是否仍然可能有内存泄漏? If yes how will that be possible ? 如果是,那怎么可能呢?

Even when using smart pointers, is it still possible to have memory leak ? 即使使用智能指针,是否仍然可能有内存泄漏?

Yes, if you are not careful to avoid creating a cycle in your references. 是的,如果您不小心避免在参考文献中创建一个循环。

If yes how will that be possible ? 如果是,那怎么可能呢?

Smart pointers based on reference counting (such as shared_ptr) will delete the pointed-to-object when the reference count associated with the object drops to zero. 基于引用计数的智能指针(例如shared_ptr)将在与对象关联的引用计数降为零时删除指向对象。 But if you have a cycle in your references (A->B->A, or some more elaborate cycle), then the reference counts in the cycle will never drop to zero because the smart pointers are "keeping each other alive". 但是如果你的参考中有一个循环(A-> B-> A,或者更复杂的循环),那么循环中的引用计数永远不会降到零,因为智能指针“保持彼此活着”。

Here is an example of a simple program that leaks memory despite using only using shared_ptr for its pointers. 下面是一个简单程序的示例,尽管仅使用shared_ptr作为其指针,但它会泄漏内存。 Note that when you run it, the constructors print a message but the destructors never do: 请注意,当您运行它时,构造函数会打印一条消息,但析构函数永远不会执行以下操作:

#include <stdio.h>
#include <memory>

using namespace std;

class C
{
public:
   C() {printf("Constructor for C:  this=%p\n", this);}
   ~C() {printf("Destructor for C:  this=%p\n", this);}

   void setSharedPointer(shared_ptr<C> p) {pC = p;}

private:
   shared_ptr<C> pC;
};

int main(int argc, char ** argv)
{
   shared_ptr<C> pC(new C);
   shared_ptr<C> pD(new C);

   pC->setSharedPointer(pD);
   pD->setSharedPointer(pC);

   return 0;
}

In addition to having circular references, another way to leak smart pointers is by doing something rather innocent looking: 除了循环引用之外,泄漏智能指针的另一种方法是做一些相当无辜的事情:

processThing(std::shared_ptr<MyThing>(new MyThing()), get_num_samples());

A person who is vaguely familiar with C++ might assume that function arguments are evaluated from left to right. 熟悉C ++的人可能会认为函数参数是从左到右进行计算的。 This is a natural thing to think, but unfortunately it is wrong (RIP intuition and principle of least surprise). 这是一个很自然的想法,但不幸的是它是错误的(RIP直觉和最少惊喜的原则)。 In fact, only clang guarantees left to right function argument evaluation (AFAIK, maybe it is not a guarantee). 事实上,只有clang保证从左到右的功能参数评估(AFAIK,也许它不是保证)。 Most other compilers evaluate from right to left (including gcc and icc ). 大多数其他编译器从右到左进行评估(包括gccicc )。

But regardless of what any specific compiler does, the C++ language standard (except for C++17, see end for details) does not dictate in what order arguments are evaluated, so it's entirely possible for a compiler to evaluate function arguments in ANY order. 但无论具体编译器做什么,C ++语言标准(C ++ 17除外,详见end)都没有规定参数的计算顺序,因此编译器完全有可能以任意顺序计算函数参数。

From cppreference: 从cppreference:

Order of evaluation of the operands of almost all C++ operators (including the order of evaluation of function arguments in a function-call expression and the order of evaluation of the subexpressions within any expression) is unspecified. 几乎所有C ++运算符的操作数的评估顺序(包括函数调用表达式中函数参数的评估顺序以及任何表达式中子表达式的评估顺序)都未指定。 The compiler can evaluate operands in any order, and may choose another order when the same expression is evaluated again. 编译器可以按任何顺序计算操作数,并且可以在再次计算相同表达式时选择另一个顺序。

Therefore, it is entirely possible that the processThing function arguments above are evaluated in the following order: 因此,完全有可能按以下顺序计算上面的processThing函数参数:

  1. new MyThing()
  2. get_num_samples()
  3. std::shared_ptr<MyThing>()

This may cause a leak, because get_num_samples() may throw an exception, and so std::shared_ptr<MyThing>() may never be called. 可能会导致泄漏,因为get_num_samples() 可能会抛出异常,因此可能永远不会调用std::shared_ptr<MyThing>() Emphasis on may . 重点可能 It is possible according to the language specification, but I actually haven't seen any compiler do this transformation (admittedly gcc/icc/clang are the only compilers I use at the time of writing). 根据语言规范,这可能的,但实际上我没有看到任何编译器进行这种转换(诚然,gcc / icc / clang是我在撰写本文时使用的唯一编译器)。 I wasn't able to force gcc or clang to do it (after about an hour of trying/researching I gave it up). 我无法强迫gcc或clang这样做(经过大约一个小时的尝试/研究我放弃了)。 Maybe a compiler expert could give us a better example (please do if you're reading this and are a compiler expert!!!). 也许编译专家可以给我们一个更好的例子(如果您正在阅读本文并且是编译专家,请执行此操作!)。

Here's a toy example where I am forcing this order using gcc. 这是一个玩具示例,我使用gcc强制执行此订单。 I cheated a bit, because it turns out it's difficult to force the gcc compiler to arbitrarily reorder the argument evaluation (it still looks pretty innocent, and it does leak as confirmed by some messages to stderr): 我作弊了一下,因为事实证明很难迫使gcc编译器任意重新排序参数评估(它仍然看起来非常无辜,并且它确实泄漏,因为某些消息已经确认为stderr):

#include <iostream>
#include <stdexcept>
#include <memory>

struct MyThing {
    MyThing() { std::cerr << "CONSTRUCTOR CALLED." << std::endl; }
    ~MyThing() { std::cerr << "DESTRUCTOR CALLED." << std::endl; }
};

void processThing(std::shared_ptr<MyThing> thing, int num_samples) {
    // Doesn't matter what happens here                                                                                                                                                                     
}

int get_num_samples() {
    throw std::runtime_error("Can't get the number of samples for some reason...and I've decided to bomb.");
    return 0;
}

int main() {
    try {
        auto thing = new MyThing();
        processThing(std::shared_ptr<MyThing>(thing), get_num_samples());
    }
    catch (...) {
    }
}

Compiled with gcc 4.9, MacOS: 使用gcc 4.9编译,MacOS:

Matthews-MacBook-Pro:stackoverflow matt$ g++ --version
g++-4.9 (Homebrew GCC 4.9.4_1) 4.9.4
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Matthews-MacBook-Pro:stackoverflow matt$ g++ -std=c++14 -o test.out test.cpp
Matthews-MacBook-Pro:stackoverflow matt$ ./test.out 
CONSTRUCTOR CALLED.
Matthews-MacBook-Pro:stackoverflow matt$

Note that DESTRUCTOR CALLED is never printed to stderr. 请注意, DESTRUCTOR CALLED永远不会打印到stderr。

You can fix this issue by ensuring that you have a different statement for creating a shared_ptr , and then pass that result to a function. 您可以通过确保创建shared_ptr语句不同,然后将该结果传递给函数来解决此问题。 This works because a compiler does not have (much) latitude between different statements (as opposed to within the same statement). 这是有效的,因为编译器在不同语句之间没有(很多)宽容度(而不是在同一语句中)。 Here's how you'd fix the toy example above: 以下是您修复上述玩具示例的方法:

// ensures entire shared_ptr allocation statement is executed before get_num_samples()
auto memory_related_arg = std::shared_ptr<MyThing>(new MyThing());
processThing(memory_related_arg, get_num_samples());

PS This is all stolen from "Effective C++", Third Edition, by Scott Meyers. PS这是Scott Meyers在“Effective C ++”第三版中偷走的全部内容。 Definitely a book worth reading if you use C++ on a daily basis. 如果你每天使用C ++,绝对是一本值得阅读的书。 C++ is hard to get right, and this book does a nice job of giving good guidelines on how to get it more right. C ++很难做到正确,本书很好地为如何使其正确提供了良好的指导。 You can still get it wrong following the guidelines dogmatically, but you'll be a better C++ dev knowing the strategies in this book. 你仍然可以按照教条的指导方式弄错,但是你知道本书中的策略是一个更好的C ++开发人员。

PSS C++17 fixes this problem. PSS C ++ 17修复了这个问题。 See here for details: What are the evaluation order guarantees introduced by C++17? 有关详细信息,请参见此处: C ++ 17引入的评估顺序保证是什么?

There are functions that release the memory from the smart pointer. 有些功能可以从智能指针释放内存。 In this case you are asking the smart pointer to stop managing the memory. 在这种情况下,您要求智能指针停止管理内存。 After that, it's up to you not to leak the memory 在那之后,你可以不泄漏内存

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM