【问题标题】:Why not always instantiate objects on the stack? C++为什么不总是实例化堆栈上的对象? C++
【发布时间】:2015-03-13 10:08:18
【问题描述】:

剧透只包含背景和上下文

This post mentions,为了解决当对象超出范围时它被释放的事实,只需将对象返回堆栈分配的对象,使其保持在范围内。这显然会在其他地方的堆栈上复制对象。 This guy 甚至确认您应该始终更喜欢分配给堆栈。 在 C++ 中,使用类似:

Object* my_object = new Object();

!动态地将一个对象实例化到堆中,然而 ! Object my_object = Object();

!实例化堆栈上的对象。堆栈的大小是有限的,而堆实际上没有(除了物理限制)。而且,根据这个this post,堆栈访问时间要快得多,当然,当它超出范围时,释放是自动的。


我正在尝试创建一个速度绝对关键的应用程序,我不能只在 main 内实例化堆栈上的所有对象,然后简单地将嵌套范围内的每个实例化保存到外部容器吗?

我自己使用包含属性“id”的简单 Node 类对此进行了测试。 我实例化了堆栈上的节点,将它们放在一个向量中,这样它们就不会被释放,然后(只是为了检查)我将新项目分配给堆栈,然后检查以确保先前分配的数据仍然存在。我可以继续在一个规模较大的问题上实施这个吗?

int main()
{
  vector<Node> stack_nodes;
  for (int i = 0; i < 2; ++i)
  {
    stack_nodes.push_back(Node(i)); // push newly copied stack-allocated objects so they don't die 
  }
  Node new_node1 = Node(3); // allocate two more to test stack memory overwriting 
  Node new_node2 = Node(4);
  cout << stack_nodes.at(1).getID(); // outputs 1! It's still there?
  return 0;
}

编辑: 请参见下面的 cmets。当您从创建它的范围返回堆栈分配的对象时,会创建该对象的副本。那个副本也在堆栈上吗?如果我将该复制的对象分配给在 main 范围内声明的向量,该对象还会在堆栈上吗?

【问题讨论】:

  • can't I just instantiate all of my objects on the stack within main, and simply save every instantiation that's within a nested scope to an outside container? 是的,当然可以。你的问题到底是什么? ,你写的代码很好。
  • "输出 1!它还在吗?" -- 我认为这让你感到惊讶吗?请注意,虽然向量本身在堆栈上,但它管理的对象却不在。内存管理细节在vector的成员函数中处理,这是应该的。当您执行stack_nodes.push_back(Node(i)); 时,Node 对象将被复制到堆上由向量对象管理的内存中。
  • “尝试创建一个速度至关重要的应用程序” - 尝试优雅地编写它,这对于初学者来说已经足够挑战了。然后担心速度如果你需要 - 使用分析器作为指导。您的程序可能已经比您需要的快得多,或者可能慢得多以至于更好的分配无法挽救它 - 至少您会知道自己所处的位置。
  • 致所有刚刚回复的人:如果 Benjamin Lindley 是正确的,那么我的问题没有得到回答,至少不是很理想。如果将新堆栈分配的对象推送到向量中会从堆栈中删除该对象并将其副本放入堆中,那么为什么不直接动态分配呢?
  • 因为那时你必须自己处理释放,这很麻烦而且容易出错。

标签: c++ memory-management scope stack


【解决方案1】:

在某些情况下,您当然可以对某些程序执行此操作。举个例子,早在很久以前,Fortran 就被定义为,程序使用的所有数据都可以静态分配(而且很正常)。

同时,它是相当有限和有问题的。仅举几个例子,它(几乎)排除了所有递归,这对于处理某些类型的递归数据结构(例如树)非常方便。

这也意味着您的所有变量本质上都变成了全局变量,因此(例如)程序中的任何代码都可以读取和/或写入程序中的几乎任何变量。使用这种模型的 Fortran 和(早期版本)BASIC 等语言的经验表明,开发目前被视为中小型程序的程序需要大量的纪律,而开发现在通常被视为大型系统的程序可能是几乎不可能。代码不同部分之间的依赖关系变得如此复杂,以至于几乎不可能确定在哪里使用了什么,哪些部分依赖于其他部分等等。

我怀疑这在实践中是否合理。分配堆栈空间的开销开始时如此微不足道,消除它根本不会显着提高速度。事实上,它可能很容易完全相反。预先分配你的变量意味着每个变量(几乎是必要的)都存在于内存的一个独特部分中。其中很大一部分将用于在给定时间当前不在缓存中的内存部分,因此最终会导致引用的局部性差,从而导致缓存使用率低。

当您输入函数时分配本地数据意味着您的大多数变量都位于或接近堆栈的顶部。由于您几乎一直在使用堆栈顶部附近的内存,因此该内存大部分时间都保留在缓存中,因此几乎所有内存访问都会命中缓存。

分配所花费的时间通常(很好)低于 1%,但访问主内存而不是缓存的代价通常至少是 10 倍,而且通常要高得多(20-50 倍很常见)。您的里程会因数据访问模式的不同而有所不同,但您有巨大的损失潜力,并且(充其量)只有很小的机会获得微小的收益。

总结:这是一个糟糕的想法。它造成很多伤害的可能性比一点点好处都多。

【讨论】:

  • 谢谢杰瑞和所有其他人。每个人的反应都很好,但我想如果我可以分配给堆栈,我会寻找更多,这不仅是可能的,而且是个好主意。
【解决方案2】:

我不能在 main 中实例化堆栈上的所有对象吗?

如果您可以考虑所有需要的对象,可以

哎呀,COBOL 将这种方法视为给定的。 “这里有你需要的每一个变量……”

我可以继续在一个规模较大的问题上实现这个吗?

有无限的记忆,是的。总是。

在内存有限的情况下,您可能希望管理对象的生命周期,并且随时只需要您实际需要的内存。

【讨论】:

    猜你喜欢
    • 2013-06-26
    • 2013-04-11
    • 2020-10-27
    • 1970-01-01
    • 2013-06-16
    • 2014-09-19
    • 2015-03-28
    • 1970-01-01
    • 2012-02-05
    相关资源
    最近更新 更多