代码之家  ›  专栏  ›  技术社区  ›  pr1268

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

  •  9
  • pr1268  · 技术社区  · 15 年前

    考虑下面的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!”带空终止符的有14个字节。如果我理解正确,12字节结构包含一个指向字符串、分配的大小和引用计数的指针(如果我在这里错了,有人会纠正我)。

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

    P.S.我在某个地方读到过 可以 报告一些使用STL容器(和“几乎容器”)的C++程序内存泄漏的假阳性,如 标准::字符串 ). 我不太担心这个漏洞,但它确实激起了我对STL容器和内存管理的好奇心。

    5 回复  |  直到 15 年前
        1
  •  9
  •   jmnas    15 年前

    其他是正确的,你正在泄漏,因为你在呼叫出口。显然,泄漏不是在堆栈上分配的字符串,而是由字符串在堆上分配的内存。例如:

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

    不会导致valgrind报告泄漏。

    泄漏是可能的(而不是确定的),因为有一个指向堆上分配的内存的内部指针。基本字符串对此负责。从我机器上的标题:

       *  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的行,您将得到一个明确的泄漏。

    山谷 documentation 有更多关于可能泄漏和内部指针的信息。

        2
  •  11
  •   GManNickG    15 年前

    打电话 exit “在不离开当前块的情况下终止程序,因此 用自动存储时间销毁任何对象”。

    换句话说,不管是否泄漏,你不应该真的在乎。当你打电话 出口 ,你的意思是“关闭这个程序,我不再关心其中的任何内容。”所以别在意了。:)

    很明显,它会泄露资源,因为您永远不会让字符串的析构函数运行,无论它如何管理这些资源。

        3
  •  4
  •   dennycrane    15 年前

    当然,这个“泄密”,通过 exit ing之前 s 的堆栈框架被留下了 s公司 是一个执行的机会。

    至于你的问题 std::string 存储:不同的实现做不同的事情。有些在堆栈上分配大约12个字节,如果字符串小于或等于12个字节,则使用该堆栈。较长的字符串将进入堆。其他实现总是堆在一起。有些是引用计数,有写时拷贝语义,有些不是。请转向斯科特·迈尔斯 Effective STL ,第15项。

        4
  •  1
  •   pm100    15 年前

    gcc STL有用于容器和字符串的专用内存池。你可以关掉这个;看看valgrind FAQ

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

        5
  •  1
  •   CashCow    15 年前

    我将避免使用Ext(),我看不出使用这个调用的真正原因。不确定它是否会导致进程立即停止而不首先清理内存,尽管valgrind看起来仍在运行。