使用C ++字符串可能发生内存泄漏

时间:2010-11-22 21:13:24

标签: c++ string memory-leaks

考虑以下C ++程序:

#include <cstdlib> // for exit(3)
#include <string>
#include <iostream>
using namespace std;

void die()
{
    exit(0);
}

int main()
{
    string s("Hello, World!");
    cout << s << endl;
    die();
}

通过valgrind运行它会显示这个(为简洁起见,一些输出被修剪):

==1643== HEAP SUMMARY:
==1643==     in use at exit: 26 bytes in 1 blocks
==1643==   total heap usage: 1 allocs, 0 frees, 26 bytes allocated
==1643==
==1643== LEAK SUMMARY:
==1643==    definitely lost: 0 bytes in 0 blocks
==1643==    indirectly lost: 0 bytes in 0 blocks
==1643==      possibly lost: 26 bytes in 1 blocks
==1643==    still reachable: 0 bytes in 0 blocks
==1643==         suppressed: 0 bytes in 0 blocks

正如您所看到的,在堆上分配的26个字节丢失了的可能性。我知道std::string类有一个12字节的结构(至少在我的32位x86 arch和GNU编译器4.2.4上)和“Hello,World!”使用null终止符有14个字节。如果我理解正确,12字节结构包含指向字符串的指针,分配的大小和引用计数(如果我在这里错了,有人会纠正我。)

现在我的问题:如何在堆栈/堆中存储C ++字符串?声明时是否存在std::string(或其他STL容器)的堆栈对象?

P.S。我读过某些地方,valgrind 可能报告一些使用STL容器的C ++程序(以及诸如std::string的“几乎容器”)的内存泄漏误报。我并不太担心这种泄漏,但它确实激起了我对STL容器和内存管理的好奇心。

5 个答案:

答案 0 :(得分:11)

调用exit“终止程序而不离开当前块,因此没有 销毁具有自动存储持续时间的任何对象“。

换句话说,泄漏与否,你真的不应该关心。当你致电exit时,你说“关闭这个程序,我不再关心其中的任何内容。”所以停止关怀。 :)

显然,它会泄漏资源,因为你永远不会让字符串的析构函数运行,绝对不管它如何管理这些资源。

答案 1 :(得分:9)

其他人是正确的,你正在泄漏,因为你正在呼叫退出。要清楚,泄漏不是堆栈上分配的字符串,而是字符串在堆上分配的内存。例如:

struct Foo { };

int main()
{
    Foo f;
    die();
}

不会导致valgrind报告泄漏。

泄漏是可能的(而不是确定的)因为你有一个指向堆上分配的内存的内部指针。 basic_string负责这一点。从我机器上的标题:

   *  A string looks like this:
   *
   *  @code
   *                                        [_Rep]
   *                                        _M_length
   *   [basic_string<char_type>]            _M_capacity
   *   _M_dataplus                          _M_refcount
   *   _M_p ---------------->               unnamed array of char_type
   *  @endcode
   *
   *  Where the _M_p points to the first character in the string, and
   *  you cast it to a pointer-to-_Rep and subtract 1 to get a
   *  pointer to the header.

它们的关键是_M_p不指向堆上分配的内存的开始,它指向字符串中的第一个字符。这是一个简单的例子:

struct Foo
{
    Foo()
    {
        // Allocate 4 ints.
        m_data = new int[4];
        // Move the pointer.
        ++m_data;
        // Null the pointer
        //m_data = 0;
    }
    ~Foo()
    {
        // Put the pointer back, then delete it.
        --m_data;
        delete [] m_data;
    }
    int* m_data;
};

int main()
{
    Foo f;
    die();
}

这将报告valgrind可能发生的泄漏。如果你注释掉我移动的行m_data,valgrind将报告'仍然可以访问'。如果您取消注释我将m_data设置为0的行,您将获得明确的泄漏。

valgrind documentation提供了有关可能泄漏和内部指针的更多信息。

答案 2 :(得分:4)

当然,在exit的堆栈帧之前s,这个“泄漏”,你不会给s的析构函数执行一次机会。

至于你的问题std::string存储:不同的实现做不同的事情。有些在堆栈上分配了大约12个字节,如果字符串是12个字节或更短,则使用该字节。更长的字符串进入堆。其他实现总是进入堆。有些是引用计数和写时复制语义,有些则不是。请转向Scott Meyers的Effective STL,第15项。

答案 3 :(得分:1)

gcc STL具有容器和字符串的私有内存池。你可以关掉它;看看valgrind FAQ

http://valgrind.org/docs/manual/faq.html#faq.reports

答案 4 :(得分:1)

我会避免使用exit()我认为没有真正的理由使用该调用。虽然valgrind似乎仍在运行,但不确定是否会在不清理内存的情况下立即停止进程。