【问题标题】:A STL object instantiated in a function is taking memory on stack or heap?在函数中实例化的 STL 对象在堆栈或堆上占用内存?
【发布时间】:2019-08-19 21:41:14
【问题描述】:

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

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 );
}

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

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

【问题讨论】:

  • “我预计对象正在从 [stack] 中占用空间,因此在函数返回后它们的内存应该被释放。”是的。当您在自动内存中创建一个对象(例如 std::string)时,该对象会从自动内存中获取它的内存。退出示波器时会释放自动内存。当 std::string 被释放时,它会释放它正在管理的任何堆内存。 (你会发现很多使用全局变量的负面 cmets ......我根本没有。)
  • 来自维基百科:“标准模板库 (STL) 是 C++ 编程语言的软件库,它影响了 C++ 标准库的许多部分。它提供了四个组件,称为算法、容器、函数和iterators.[1]" 我认为您可能使用的是编译器附带的“C++ 标准库”,而不是 STL。

标签: c++ stl containers dynamic-memory-allocation lifetime


【解决方案1】:

以下是所发生情况的细分。

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

在您的foo() 函数中,您在堆栈上创建您的类MyObj 的一个实例,然后将它的副本 添加到您的全局向量中。

可以通过监控MyObj的拷贝构造函数来检查是否被拷贝,如图here

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

【讨论】:

  • 太棒了。非常感谢。我认为这意味着,添加指向容器的指针比对象本身更有效吗?我唯一的问题是编码风格变得有点难以遵循。例如,使用 map,您可以使用 [] 访问元素,但使用指针,您必须重复使用引用,例如 (*myMap)[key] 而不是 myMap[key]。您对此有任何见解或 cmets 吗?
  • 指针有其自身的一系列问题,但您可以考虑避免不必要的对象副本的一个方向是移动语义
【解决方案2】:

根据我的经验,情况并非如此,即容器中的对象可以在函数外部访问,到目前为止没有问题。

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

有人能告诉我为什么会这样吗?

你已经找到解释了。

STL 容器是否复制传递给它们的对象的数据并将其保存在堆或全局内存中的某个位置?

对于您使用std::vector 的用例也是如此。一般来说,这不是真的。 std::array 是一个例外。它不使用堆内存。

【讨论】:

  • 小警告:std::array
【解决方案3】:

STL 容器是否复制传递给它们的对象的数据并保留它 在堆中的某个地方还是在全局内存中?

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

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

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

{
   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 个字节,在这种情况下“在自动内存中”(有时称为堆栈)。将 100 个字符添加到 a_s 后,第 100 个元素 std::string 使用...是的,只有 32 个字节(也在自动内存中)。

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

那么,“这 100 个字符存储在哪里?”是一个很好的问题,但不是你需要担心的。使用对象方法(用于访问和修改),您应该没有问题。

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

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

【讨论】:

  • 非常感谢您的评论。
猜你喜欢
  • 2015-03-13
  • 2012-10-04
  • 2021-01-19
  • 1970-01-01
  • 2011-08-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-25
  • 2016-03-06
相关资源
最近更新 更多