列表和列表元素,存储在哪里?

时间:2012-01-16 15:47:10

标签: c++

鉴于这段代码:

#include <list>
(void) someFunction(void) {
    list <int> l;
    l.push_back(1);
}
  • 列表中的元素存储在哪里?堆?堆?
  • 如何凭经验检查值是在堆栈还是堆中?
  • 此功能可以返回列表吗? 已编辑如果我将函数声明为list,是否可以返回没有问题的列表?

示例(返回列表):

#include <list>
list<int> someFunction(void) {
    list <int> l;
    l.push_back(1);
}

...
l2 = someFunction();
l2.push_back(2);

7 个答案:

答案 0 :(得分:5)

  

列表中的元素存储在哪里?堆?堆?

列表元素存储在堆上。你可以在push_back方法调用的调试器后面看到这个。最容易看到的是存储对象而不是POD类型,并记录构造函数。你需要复制构造函数,因为它们被复制了。分配发生在模板参数分配器中,您可以指定它,也可以不指定它,它将使用默认的堆分配。

  

如何凭经验检查值是在堆栈还是堆中?

您可以使用堆栈中的push_back元素进行检查:

std::list<int> my_list;
int a = 10;
my_list.push_back(a);
a = 11;
assert(*my_list.begin() == 10);
  

此函数可以返回列表吗?

在C ++中,有两种传递数据的方法:引用或按值。如果你的功能看起来像这样,

list<int> func()
{
  list<int> res;
  res.push_back(10);
  return res;
}

然后你按值传递列表,这意味着编译器将调用列表的复制构造函数,该构造函数也复制列表中的所有值。当函数返回时,在复制列表之后,将调用“res”列表的析构函数,释放其所有元素。但是,如果你这样做:

list<int>& func()
{
      list<int> res;
      res.push_back(10);
      return res;
}

当您返回对“res”列表的引用时,您的代码将失败,该列表将在其作用域的末尾被销毁,因此您的引用将无效。

第一个解决方案的问题可能是性能问题。你也可以在没有复制构造函数调用的情况下这样做:

void func(list<int>& res)
{
  res.push_back(10);
}

list<int> list_to_fill;
func(list_to_fill);

在这种情况下,没有复制,它应该更快,因为只有一个列表创建。

答案 1 :(得分:3)

  

列表中的元素存储在哪里?堆?堆?

列表中保存的元素通常是动态分配的,因此它将在堆上。

  

此函数可以返回列表吗?

不,它不能,你声明你的函数返回类型为void。

  

如何凭经验检查值是在堆栈还是堆中?

唯一可靠的方法是查看std :: list实现,看看它到底在做什么。但是你真的不需要关心这个,因为它是一个实现细节。

答案 2 :(得分:1)

如果更改了函数签名以返回列表,则确实可以返回列表;但是编译器会对列表进行复制,并且副本是调用者将接收的副本。

原始列表将在函数结束时销毁,但在此之前将进行复制,因此您将返回有效数据。如果你试图欺骗系统并返回指针或引用而不是复制,那就是当你遇到未定义的行为时。

列表本身将在堆栈中,但其中包含的元素将在堆上。该列表将包含它在幕后管理的指针,因此您根本不必担心存储。

答案 3 :(得分:1)

其中一些可能会因编译器和/或库作者的异想天开而异。

就像现在一样,编译器很可能会检测到创建和填充列表的结果从未被使用过,因此它会将整个函数作为死代码消除。为了防止这种情况,您可以(例如)返回列表,而不是仅仅在函数结束时将其销毁。

这只会增加新的可能性。大多数编译器实现返回值优化(RVO)和命名返回值优化(NRVO)。这些基本上意味着当/如果你返回一个值(例如你的list对象)而不是在堆栈上创建一个对象并在它返回时将它复制到目标,编译器生成代码以生成返回后将分配的对象。在这种情况下,该函数根本不会创建一个本地函数,只会收到一个指向它将写入结果的位置的隐藏指针。

std::list通常使用std::allocate为要存储的数据分配空间。但是,您可以为其指定不同的分配器。在这种情况下,空间几乎可以分配到任何地方。

虽然它对std::string之类的其他(某种)容器更常见,但也有可能(至少在某些情况下)在对象本身中存储至少少量数据,并且只在其上分配空间当/如果溢出时堆。这对于维护异常安全等有一些限制,但在list<int>的情况下,它应该不是问题。例如,前十个int可能存储在list对象本身中,并且只有当/如果添加更多时,它才会在堆上分配空间。

答案 4 :(得分:1)

返回函数声明如下:list<int> somefunc() {list<int> L; return L; }

使用list<int>::iterator检查值。

像这样:

#include <iostream>
#include <list>
using namespace std;


list<int> someReturnFunc(int listSize)
{
    list<int> myList;

    for (int g=0; g< listSize; g++)  myList.push_back(g);

    return myList;
}

int main ()
{
    list<int> yourList;
    list<int>::iterator i;

    yourList = someReturnFunc(15);

    for(i=yourList.begin(); i != yourList.end(); ++i) cout << *i << " ";

    cout << endl;

    return 0;

}

答案 5 :(得分:1)

你可以这样做(只要我理解你的问题)

#include <iostream>
#include <list>

using namespace std;

list<int> & fun()
{
    list<int> & temp = *(new list<int>);
    temp.push_back(4);
    // you can do other operations on the list
    return temp; // you pass the reference, any list will not be destroyed neither copied.
}

int main()
{
    list<int> & my_list = fun();

    cout << *(my_list.begin()) << endl;
}

答案 6 :(得分:1)

您选择的答案不正确1件事......

返回列表不会调用列表的复制构造函数。

list<int> func()
{
  list<int> res; // &res returns 0x7fff183f0900
  res.push_back(10);
  return res;
}

list<int> l = func();   // &l also returns 0x7fff183f0900 (no copy)

您可以通过在功能中打印&amp;和在功能外部&amp; l来仔细检查。

编译器经过优化,可以在不进行复制的情况下传递返回的对象,否则会复制返回的每个非指针对象,这将是疯狂的。