2016-12-06 74 views
4

我想问一个关于构造函数中内存泄漏的问题。让我们考虑一个类:std :: make_unique <T> vs reset(new T)

class Foo 
{ 
    public: 
     Foo(){ throw 500;} 
}; 

是什么在我看来

std::unique_ptr<Foo> l_ptr = std::make_unique<Foo>(); 

std::unique_ptr<Foo> l_ptr; 
l_ptr.reset(new Foo()); 

之间的差异与make_unique的解决方案应该保护我不受内存泄漏,但在这两种情况下,我得到相同的valgrind结果:

$ valgrind --leak-check=full ./a.out 
==17611== Memcheck, a memory error detector 
==17611== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. 
==17611== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info 
==17611== Command: ./a.out 
==17611== 
terminate called after throwing an instance of 'int' 
==17611== 
==17611== Process terminating with default action of signal 6 (SIGABRT) 
==17611== at 0x5407418: raise (raise.c:54) 
==17611== by 0x5409019: abort (abort.c:89) 
==17611== by 0x4EC984C: __gnu_cxx::__verbose_terminate_handler() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21) 
==17611== by 0x4EC76B5: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21) 
==17611== by 0x4EC7700: std::terminate() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21) 
==17611== by 0x4EC7918: __cxa_throw (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21) 
==17611== by 0x40097B: Foo::Foo() (in /home/rungo/Repositories/test/a.out) 
==17611== by 0x4008DC: main (in /home/rungo/Repositories/test/a.out) 
==17611== 
==17611== HEAP SUMMARY: 
==17611==  in use at exit: 72,837 bytes in 3 blocks 
==17611== total heap usage: 4 allocs, 1 frees, 72,841 bytes allocated 
==17611== 
==17611== 132 bytes in 1 blocks are possibly lost in loss record 2 of 3 
==17611== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) 
==17611== by 0x4EC641F: __cxa_allocate_exception (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21) 
==17611== by 0x400963: Foo::Foo() (in /home/rungo/Repositories/test/a.out) 
==17611== by 0x4008DC: main (in /home/rungo/Repositories/test/a.out) 
==17611== 
==17611== LEAK SUMMARY: 
==17611== definitely lost: 0 bytes in 0 blocks 
==17611== indirectly lost: 0 bytes in 0 blocks 
==17611==  possibly lost: 132 bytes in 1 blocks 
==17611== still reachable: 72,705 bytes in 2 blocks 
==17611==   suppressed: 0 bytes in 0 blocks 
==17611== Reachable blocks (those to which a pointer was found) are not shown. 
==17611== To see them, rerun with: --leak-check=full --show-leak-kinds=all 
==17611== 
==17611== For counts of detected and suppressed errors, rerun with: -v 
==17611== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) 
[1] 17611 abort (core dumped) valgrind --leak-check=full ./a.out 

当我使用clang ++和g ++时,它是一样的。 我发现这里:https://isocpp.org/wiki/faq/exceptions#ctors-can-throw了一句:

注:如果抛出一个异常,与本身清理对象相关的内存构造完成 - 有没有内存泄漏。

我的问题是,为什么我们在这种情况下,泄漏,为什么make_unique没有阻止泄漏(doeas这意味着有make_unique和复位(新...)之间没有dofference?

+0

你有没有发现异常? – Danh

回答

9

是你捕捉异常?当捕捉异常,Valgrind的没有检测到泄漏都与make_uniquereset(与G ++ 6.2-g编译)。

int main() try 
{ 
#if TEST_MAKE_UNIQUE 
    std::unique_ptr<Foo> l_ptr = std::make_unique<Foo>(); 
#else 
    std::unique_ptr<Foo> l_ptr; 
    l_ptr.reset(new Foo()); 
#endif 
} 
catch(...) 
{ 

} 

AddressSanitizer也不报告任何问题。

(附注:这是一个很好的机会展示鲜为人知function-try-block语言功能)。


“为什么不内存泄漏得到?”

标准保证与new表述”分配的内存会如果一个异常被构造期间抛出被自动释放。

From $15.2.5:

如果对象是由一个新的表达式分配([expr.new]),匹配释放函数([basic.stc.dynamic.deallocation]),如果有的话,被称为释放对象占用的存储空间。


相关问题:

+0

valgrind帮助捕捉异常,但我的问题是关于这两种方法之间的区别?在这种情况下他们是否平等? – rungus2

+5

你可能想提一下,当异常未被捕获时,标准明确地允许堆栈展开*不*发生。 – Angew

+0

@ rungus2:你能不能更新你的问题来问问你的意思? –

2

make_unique可以用来避免在某些情况下,内存泄漏,如:

int foo(); 
void bar(unique_ptr<int> a, int b); 

int main() 
{ 
    try 
    { 
     bar(unique_ptr<int>(new int(5)), foo()); 
    } 
    catch (...) {/*TODO*/} 
} 

在此有可能使该呼叫new先发生,并调用foo()然后可以将unique_ptr<int>施工前发生。如果foo()抛出,则int将被泄漏。

int foo(); 
void bar(unique_ptr<int> a, int b); 

int main() 
{ 
    try 
    { 
     bar(make_unique<int>(5), foo()); 
    } 
    catch (...) {/*TODO*/} 
} 

在这种情况下,无论是foo()首先调用,没有创建int如果它抛出,或make_unique<int>(5)首次调用,并允许完成:如果make_unique来代替这是不可能的。如果foo()然后抛出,int将通过临时unique_ptr<int>的析构函数在堆栈展开期间被删除。

如果您没有将其他内容放入相同语句中,如调用l_ptr.reset(new Foo());,则make_unique不会提高安全性。不过,它可能还是比较方便的。

如果您没有发现抛出的异常,堆栈可能会或可能不会解开。在多线程程序中,您甚至可以通过让异常“从线程中”逃脱来触发未定义的行为。总之,不要那样做。

UPDATE在C++ 17,上述bar(unique_ptr<int>(new int(5)), foo());也异常安全的,因为函数参数的评价不再未测序但现在不定测序。这意味着编译器必须保证有一个订单,它只是不必告诉你哪个订单就是。请参阅Barry对this question的回应。

+0

看起来好像你在说函数调用中的一个参数的评估可能发生在函数调用中另一个参数的评估的_middle_中。标准实际上允许吗? –

+1

是的,这是允许的。函数参数评估的副作用发生的顺序未指定(必须在进入函数之前完成)。这么晚才回复很抱歉。请参阅https://stackoverflow.com/questions/2934904/order-of-evaluation-in-c-function-parameters –

0

正如其他回复中所提到的,valgrind抱怨说由于存在未捕获的异常而导致内存泄漏,而这种异常又调用std::terminate,这反过来使所有内容都保持原样。如果可执行程序没有终止(例如,您在某处捕获了异常),则内存将由语言定义的new行为自动释放。

看起来好像你在问为什么std::make_unique存在更深的问题。这存在类似的情况:

some_func(new Foo, new Foo); 

在这样一个情况下,语言不作任何时候new被调用时,对被Foo::Foo()称为保证。你可能让编译器组织一些东西,这样new被调用两次,一次为第一个参数分配空间,一次为第二个参数分配空间。然后Foo()被构造,抛出异常。第一次分配得到清理,但第二次分配会泄漏,因为没有施工(或有例外)!队列make_unique

some_func(std::make_unique<Foo>(), std::make_unique<Foo>()); 

现在我们正在调用一个函数,该new将在构造函数的顺序每个参数之前调用。如果第一个参数引发异常,则第二个分配甚至不会发生。

VS. unique_ptr::reset我们只是有方便。由于引发异常的定义new行为,您不应该看到任何内存泄漏问题。

相关问题