堆内存与栈内存分配

11

关于在堆上分配对象和在栈上分配对象,以及何时以何种方式调用delete(),我有点困惑。

例如,我有一个名为Vector的类。我想创建一个这样的数组。

我可以选择这样做:

Vector** v = new Vector*[100]; //create an array of 100 pointers to 100 Vector objects

据我理解,这会将所有东西(除了指针地址)都分配在堆上?
因此要释放内存,我需要:

for (int i = 0; i < 100; ++i)
{
   delete(v[i]);
}
delete(v);

或者仅仅

delete(v);

够了吗?

现在来看另一个例子:

Vector* v = Vector[100];

在这种情况下发生了什么?分配发生在堆还是栈上?我是否仍需要调用

delete(v);

但这并不是全部问题,对于长篇帖子我表示抱歉。

class Vector
{
  int x, y, z;
}

Vector* v = new Vector();

x、y、z变量在哪里分配?堆还是栈?

或者这样问:

class Vector2
{
   int items[10];
}

Vector2* v2 = new Vector2();

items[10]是在哪里分配的? 我该如何删除v2?我需要自定义析构函数吗?

最后但同样重要的是,这个问题怎么样?

class Vector3
{
   int* items;
}

Vector3 v3 = Vector3();

items指针存储在堆栈中还是堆中?如何删除它?

感谢您,对于这个长问题很抱歉。我已经有一段时间遇到这些问题了,在网上也没有找到完整的解释。


2
忘掉new/delete,你不需要它们。只需使用std::vector即可。 - Martin York
抱歉,伙计,这只是我用来理解内存分配的示例类。这与实际应用程序无关。我也可以称其为MyClass。即使它是std :: Vector,也是另一回事,请不要混淆名称! - Chebz
这与名称无关,而与使用“new X []”有更多关系。在大多数情况下,您应该使用“std :: vector <X>”,而不是这样做。 - Martin York
4个回答

14

我将从头开始...

Vector** v = new Vector*[100];

在堆上分配一个包含100个指向类型为Vector的对象的指针数组。它返回一个指针v,您可以使用它来跟踪这个指针数组。

使用以下方法删除这个100个指针的数组:

delete[] v;

使用delete运算符删除单个分配的对象,使用delete[]运算符删除数组。

下一个情况(我假设你指的是new Vector[100]):

Vector* v = new Vector[100];
您在堆上分配了一个包含100个向量的数组,并获得了指向其起始位置的指针v。 请使用以下语句删除此数组:

delete[] v;

delete[] v;

下一步...

class Vector
{
  int x, y, z;
}

Vector* v = new Vector();

这将在堆上分配一个Vector类的对象,并给您一个指针来跟踪它。由于您在堆上分配了整个对象,因此x,y和z都在堆上分配。

使用以下代码删除它:

delete v;


class Vector2
{
   int items[10];
}

Vector2* v2 = new Vector2();
这个有点棘手,但是我会推理一下...
类是蓝图。除非你以某种方式实例化该类(例如在堆上),否则根本没有分配任何内存。因为类是一个蓝图,所以在你在堆上创建一个 Vector2 类的对象之前,items 不能被分配。我认为我们可以合理地推断出 items 是在堆上分配的。
使用以下代码删除 v2:
delete v2;

最后:

class Vector3
{
   int* items;
}

Vector3 v3 = Vector3();

你在栈上分配了整个 Vector3 类,其中的指针 items 也是这样分配的。没有任何东西进入堆,所以不要删除它。


我认为一个简单的 delete[] v; 可能会导致一些内存泄漏(如果分配的指针最终被使用)。 - user418748
3
在你开始考虑模板之前,先不要考虑模板。你需要正确理解动态内存的概念。模板是一种完整的子语言(你需要先理解主语言)。请忘记模板。 - Martin York
@Martin: 我并不想给人们传达C++中模板的实际意义(例如vector<myclass>等)。我试图表达的是当类声明被创建时,它作为将来该类分配的蓝图。 另外,我认为Naumov使用Vector的意义是指X、Y、Z值(符合三维坐标系的思维方式),而不是STL模板的意义。它们是两个不同的概念。 - Prime
@Muggen:他只在第一次调用时分配了一个包含100个指针的数组。只有当他实际上使用这些指针进行某些操作,例如使用它们分配100个向量对象时,才会出现内存泄漏的情况。但是他没有这样做,所以简单的delete[]就足够了。 - Prime
@dragonwrenn:在谈论C++时过度使用术语“模板”是一个不好的想法。请将您的短语更改为:“类是蓝图。” - Martin York
显示剩余2条评论

10

首先,你可能不需要动态分配任何东西。使用静态数组或向量会更好。

但是假设你正在进行这个练习。

1 分配指针数组

Vector** v = new Vector*[100]; //create an array of 100 pointers to 100 Vector objects
                               //
                               // The above comment is misleading.
                               // The 100 pointers have not been initialized.

这分配了一块内存空间,可容纳100个指向Vector的指针(注意它们未初始化,即每个指针都是随机的)。要删除它,你需要执行以下操作:

delete [] v;

2 分配数组成员

如果您想使用数组的每个成员,您应该为它们分别进行分配:

for (int i = 0; i < 100; ++i)
{
   v[i] = new Vector;
}

// Code

for (int i = 0; i < 100; ++i)
{
   delete v[i];
}

请注意,每次调用new都应该对应一次调用delete。

3 一个错误

Vector* v = Vector[100];

这是错误的。它将无法编译。

4 成员变量存放在哪里

除非成员变量是指针,否则它们位于对象内部。
如果成员变量是指针,则必须单独分配空间。

class Vector
{
  int x, y, z;
}

Vector* v1 = new Vector();
Vector  v2 = Vector();      // Yes the = Vector() is required

这里v1指向一个动态分配的对象,该对象包含x/y/z。
而v2是一个包含x/y/z的对象。

我知道有人会说= Vector();是不必要的或者是拷贝构造函数。这两种说法都有点正确,但都没有理解到重点。1) 这是一个拷贝构造函数,但编译器总是足够聪明以将其优化掉。2) 它是必需的,以使其与上面的行等效。区别在于,如果没有它,就会进行默认初始化(即未初始化),而使用它则会对成员进行零初始化(因为Vector()只有一个编译器生成的构造函数)。

那么数组成员呢?
它们与其他成员没有任何区别。成员始终在对象内部分配。如果成员是指针,则它在对象内部,但它所指向的内容必须显式设置。

class Bob
{
    int   dataArray[10];
    int*   dataPtr;
};
Bob  b1 = Bob();
Bob* b2 = new Bob();

b1.dataArray[0] = 1;            // dataArray is inside the object.
                                // b1 is allocated locally

b1.dataPtr      = new int [10]; // dataPtr is inside the object.
                                // But what it points at must be seprotally defined.
                                // Note you must call delete [] for each new []
b1.dataPtr[5] = 2;

b2->dataArray[0] = 1;           // dataArray is inside the object.
                                // b2 is allocated dynamically

b2->dataPtr      = new int [10];// dataPtr is inside the object.
                                // But what it points at must be aseptically defined.
                                // Note you must call delete [] for each new []
b2->dataPtr[5] = 2;

2

通用规则:

  • 当使用 newnew [] 时,你在堆上分配内存,在其他情况下你在栈上分配内存。

  • 每次使用 new 时,你都应该使用 delete(显式或隐式)。

  • 每次使用 new[] 时,你都应该使用 delete[](显式或隐式)。

下面的代码是未定义行为,因为你使用了一个 new[] 和101个 delete。 使用一个 delete[]

Vector** v = new Vector*[100];
for (int i = 0; i < 100; ++i)
{
   delete(v[i]);
}
delete(v);

未定义行为是指语言标准没有完全规定的情况,因此每个语言实现都可以按照自己的方式进行操作。它可能会崩溃、损坏您的数据或发射航天飞机。 - Yuras

1
  1. delete [] v; - 这是delete操作符的数组表示法。当删除数组时,你必须使用它,而不是按顺序删除每个元素。
  2. Vector* v = Vector[100];这样写无法编译通过。改为Vector v[100];,它会在堆栈上分配一个向量数组。你不需要手动删除它。
  3. x, y, z作为整个对象位于堆上。
  4. items[10]也被分配在堆上,因为它是对象的一部分。要删除它,只需调用delete v2;。除非在构造函数中分配了任何内容,否则不需要特殊的析构函数。
  5. int* items;存储在堆栈上,因为它是在堆栈上分配的对象的一部分。你不需要删除它,它会在超出范围时自动删除。

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