在函数中实例化的 STL 对象正在占用堆栈或堆上的内存?

A STL object instantiated in a function is taking memory on stack or heap?

我正在向函数中的向量添加对象。代码如下所示:

class MyObj
{
   int a;
   int b;

   MyObj( int ai, int bi )
   {
      this->a = ai;
      this->b = bi;
   }
};

vector<MyObj> myVec;

void foo()
{
    MyObj objInst( 10, 20 );
    myVec.push_back( objInst );
}

我预计对象正在从堆栈中获取 space,因此在函数 return 之后它们的内存应该被释放。根据我的经验,情况并非如此,即到目前为止,可以在函数外部访问容器中的对象而没有问题。 有人可以告诉我为什么会这样吗? STL 容器是否复制传递给它们的对象的数据并将其保存在堆中或全局内存中的某个位置?

非常感谢@Louen 的评论。读了这篇文章,学到了很多。 https://www.internalpointers.com/post/c-rvalue-references-and-move-semantics-beginners

To my experience that's not the case i.e the objects in the container can be accessed outside the function with no problem so far.

您在容器中看到一个副本,而不是在 foo 中的堆栈 space 中创建的对象。

Can someone please enlighten me why that's the case?

你已经找到了解释。

Do STL containers copy the data of the object passed to them and keep it somewhere int the heap or in the global memory?

您的用例 std::vector 也是如此。一般情况下并非如此。 std::array 是个例外。它不使用堆内存。

这是发生的事情的细目。

在函数体外部定义的向量 myVec 是一个 global 变量。 这意味着它在程序执行开始时创建(在调用 main() 之前)并在结束时销毁(当 main() returns 时)。

在您的 foo() 函数中,您在堆栈上创建 class MyObj 的实例,然后添加它的 copy到您的全局矢量。

可以通过监控MyObj的复制构造函数来查看是否被复制,如图here.

foo() 结束时,您创建的 MyObj 实例将被销毁,但从副本创建的实例将继续存在于全局向量中。它仅在全局向量本身被销毁时才被销毁,即在 main().

结束后

Do STL containers copy the data of the object passed to them and keep it somewhere int the heap or in the global memory?

是的。 (但请注意,您在 std C++ 中使用的大多数容器不再是 'STL' 的一部分……更多研究供您参考。)

  • 考虑使用 sizeof() 作为您调查的简单工具。

我几乎在所有代码中都使用 std::string,因此请考虑以下代码片段:

{
   std::string       a_s;    // empty string

   std::cout << "\n\n"
      << "\n  sizeof(empty std::string         type) " << std::setw(5) 
      << sizeof(std::string)
      << "\n  sizeof(empty std::string       object) " << std::setw(5) 
      << sizeof(a_s) << std::endl
      << "\n  a_s.size(                              " << std::setw(5) 
      << a_s.size()
      << "\n  contents                                   '" << a_s << "'" 
      << std::endl;

   // add 100 chars into a_s
   a_s += "50 chars>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>|"
      "\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<50 chars";

   std::cout
      << "\n  sizeof(100 char string         object) " << std::setw(5) 
      << sizeof(a_s)
      << "\n  a_s.size()                             " << std::setw(5) 
      << a_s.size()
      << "\n  contents                                 '" << a_s << "'"  
      << std::endl;
}

输出:

  sizeof(empty std::string         type)    32
  sizeof(empty std::string       object)    32

  a_s.size(                                  0
  contents                                   ''

  sizeof(100 char string         object)    32
  a_s.size()                               100
  contents                                 '50 chars>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>|
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<50 chars'

(抱歉,这种格式对于 SO 来说太长了……100 个字符在我的控制台上显示正常。)

std::string 使用 32 个字节,在此上下文中 'in the automatic memory'(有时称为堆栈)。在 a_s 添加 100 个字符后,第 100 个元素 std::string 使用...是的,只有 32 个字节(也在自动内存中)。

然而,a_s.size() 报告该字符串中有 100 个字符。

因此,'where are the 100 chars stored?' 是一个很好的问题,但不是您需要担心的问题。使用对象方法(用于访问和修改),你应该没有问题。

请注意,某些 std::string 代码试图将小字符串打包到对象中,因此不使用堆。我的LUBuntu用的是g++ v8.3.0,'small'std::strings是不是用0堆,我还没有调查。因为我不确定我会因为那个实现细节而编写不同的代码。

总结:在您阅读的某个地方,您错过了标准容器(例如 std::string)是与 C 字符串相比更复杂的对象的想法。这些标准容器对象的代码管理堆的使用,std::string 内容存储在堆上(对于大多数实现)。 'heap management' 是大多数标准容器的共同成就。