如何确定一个对象应该放在堆栈上还是堆上?

7

我在寻找一个C++中关于在堆栈或堆上分配对象的经验法则。我在SO上找到了很多讨论,很多人说,这取决于对象的生命周期。如果你需要比函数作用域更长的生命周期,就把它放在堆上。这是完全有道理的。

但是让我困惑的是,很多人说,如果对象很小,就将其分配到堆栈上。如果对象很大,就将其放到堆上。但是他们没有说明如何确定一个对象是否很大?

我有以下问题:

  1. 如何确定一个对象是否很大?
  2. 堆栈最大大小是多少?每个操作系统的堆栈大小都不同吗?
  3. 我有一个包装vector<string>的包装类。它将有大约100个项目。如果我将这个类分配到堆栈上,会导致堆栈溢出吗?我尝试了一下,但它完美地工作了。不确定我做错了什么。
6个回答

10

首先,向量(以及所有STL容器类)总是从堆中分配内存,因此您不必担心这个问题。对于任何具有可变大小的容器,基本上不可能使用堆栈。

如果您考虑堆栈分配的工作方式(在编译时,基本上通过为每个对象递增指针来完成),那么应该清楚地了解向量内存来自堆。

std::vector<int> myInts;
std::string myString;
SomeOther Class;

// this memory must come from the heap, there's no way it
// can now be allocated on the stack since there are other
// objects and these parameters could be variable
myString = "Some String";
myInts.reserve(256);

除非你在递归函数中,否则可以将几千字节的数据放在堆栈上而不必担心太多。堆栈大小由程序(而不是操作系统)控制,其默认值通常在32kb-1mb范围内。大多数桌面软件的堆栈大小为1mb。

单个对象几乎从不是问题。一般来说,它们要么足够小以适应堆栈,要么会从堆中进行内部分配。

如果对象是函数的局部变量,请将它们放在堆栈上。否则就将它们放在堆上。

使用堆来存储用于加载/排序/操作数据的大缓冲区。


用于手动加载/排序/操作数据。如果您使用STL来为您完成工作,则无需费心和麻烦地使用new/delete。只需在堆栈上分配即可。 - strager
谢谢你的回答。你有没有任何文件,说明向量在堆上分配内容? - Navaneeth K N
我已经更新了我的答案,以展示为什么向量(以及一般的集合)必须从堆中分配内存。 - Andrew Grant
那很有道理。谢谢。 - Navaneeth K N
两个小问题:(1) 向量(vector)和字符串(string)可能包含一个小的初始缓冲区,以避免堆分配,在字符串中这被称为小字符串优化,在向量中我不知道是否有任何实现真正使用了这种优化。(2)递归期间无论你将多少东西放在堆栈上(只要少于堆栈帧),整个堆栈帧都会被推入,即使没有完全使用它。 - Motti

4
根据MSDN,堆栈大小默认为1mb。(这是针对Msdev的)
从文章中可以看出,您可以使用/F标志在编译时修改堆栈大小。
我认为您关于堆栈与堆使用的第一个指南相当准确,但需要说明的是,如果您的临时变量大于1mb,请将其放在堆上(并可能询问为什么首先分配那么多内存以供短时间使用)。

3

如果要在堆栈上分配大型对象,唯一的方法是在某个时刻涉及旧式数组。例如:

void f() {
   char a[1000000];    // big object on the stack
}

struct A {
   char c[1000000];
};

void g() {
   A a;      // another big object on the stack
}

如果您不使用数组(而且您确实不应该使用),那么大多数东西都会在堆上为您分配:

void h() {
   std::string s( 100000 );
}

上述代码在栈上为指针、大小信息等分配了一些字节,然后在堆上分配实际存储空间。
所以不要担心!你可能正在正确地执行任务!

那些不是“旧式数组”。它们就是数组。你不应该永远不使用它们,并且你应该知道如何(以及何时)使用它们。 - strager
谢谢你的回答。但是你给出的最后一段代码为什么会进入堆内存?你的意思是说std::string在内部使用堆来保存内容吗? - Navaneeth K N

1

1. 如何判断一个对象是否大?

使用 "sizeof"。

class c {
  std::vector<std::string> s;
};

int size = sizeof(c);

在我的机器上,“size”为16个字节。

2. 栈的最大大小是多少?每个操作系统的栈大小都不同吗?

你无法确定,但这绝对不是分配大量数据的好地方。

3. 我有一个包装向量的包装类。它将有大约100个项目。如果我将此类分配到堆栈中,会导致堆栈溢出吗?我尝试过,但它完美地工作了。不确定我是否做错了什么。

不会。std::vector在堆中分配了100个项目。


1
如何判断一个对象是否大?取决于您的编译器/平台组合。没有一个真正的限制。编译器通常允许您调整此设置。
堆栈最大大小是多少?每个操作系统都有不同的堆栈大小?主要取决于上述因素。唯一的问题是您无法控制调整。
我有一个包装向量的包装类,它将有大约100个项目。如果我将此类分配给堆栈,会导致堆栈溢出吗?我尝试过这样做,但它完美地工作了。不确定我是否做错了什么。
只要该包装器和该块中的其他对象的总内存要求不超过您的堆栈帧大小,就可以正常工作。这又取决于平均字符串大小。
一个好主意是通过调试器逐步执行并查看堆栈地址-这将在某种程度上为您提供宽度的起点。还有文档。

1

我要提到的另一点是,因为vector<>必须能够重新调整大小,如果它增长到足够大(参见下文),即使vector<>本身被声明为堆栈变量,它也必须使用堆来存储其包含的对象。

[编辑]正如Motti在评论中指出的那样,vector<>可能会在其堆栈分配的对象内部保留少量空间,作为小向量的优化。在这种情况下,处理足够小以适合此空间的向量将不需要任何堆分配。(为避免处理较小的向量时浪费空间,该预分配的空间必须相当小。)无论如何,如果向量增长到足够大,它都将需要在堆上进行(重新)分配。


1
不是真的,它可能使用对象的初始缓冲区(例如小字符串优化)。 - Motti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接