【问题标题】:Possible memory leak using C++ string使用 C++ 字符串可能存在内存泄漏
【发布时间】:2011-05-14 02:40:41
【问题描述】:

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

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

附:我在某处读到 valgrind 可能在一些使用 STL 容器(和“几乎容器”,例如 std::string)的 C++ 程序中报告内存泄漏的误报。我不太担心这种泄漏,但它确实激起了我对 STL 容器和内存管理的好奇心。

【问题讨论】:

  • @EboMike:我编写了die() 函数将exit(0) 调用与main() 分开。我的程序现在必须将控制权转移给一个被调用的函数,其中exit(0) 函数在该程序的执行中“拔掉插头”。请记住,除了学术原因之外,该计划没有任何用处。
  • 我的意思是 die() 首先导致泄漏。如果你一心想在里面放一个 die(),至少把字符串放到它自己的范围内。
  • 换句话说,“我射中了自己的脚,然后我泄了气。”我的建议 - 使用止血带。

标签: c++ string memory-leaks


【解决方案1】:

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

换句话说,无论是否泄漏,您都不应该真正关心。当您致电exit 时,您是在说“关闭此程序,我不再关心其中的任何内容”。所以别在乎了。 :)

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

【讨论】:

  • 与多线程相关,如果exit在子线程中被调用,整个进程会停止,还是只在调用它的线程中停止?
  • @Matt:据我所知,它的行为就像其他线程不存在一样,然后也是如此。即清理线程局部静态,然后程序静态等。
【解决方案2】:

其他都是正确的,您正在泄漏,因为您正在调用退出。需要明确的是,泄漏不是在堆栈上分配的字符串,而是由字符串在堆上分配的内存。例如:

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 有更多关于可能的泄漏和内部指针的信息。

【讨论】:

  • 感谢您的回答。这解释了很多关于 C++ 字符串及其分配的内容。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

    gcc STL 有用于容器和字符串的私有内存池。您可以将其关闭;查看 valgrind 常见问题解答

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

    【讨论】:

    • 虽然很多年前确实如此,但现在 GCC 的标准库在其默认配置中不使用任何内存池。
    【解决方案5】:

    我会避免使用 exit() 我认为没有真正的理由使用该调用。不确定是否会导致进程立即停止而无需先清理内存,尽管 valgrind 似乎仍在运行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-04
      • 1970-01-01
      • 2014-04-30
      • 1970-01-01
      • 2011-04-18
      • 1970-01-01
      • 2011-08-29
      相关资源
      最近更新 更多