本文参考自:https://www.devbean.net/2014/02/cpp-create-object-on-heap-or-stack/ 觉得对初学者有些帮助。

关于究竟是在堆上还是在栈上创建对象,可能很多初学者感到迷惑。我想可以把这部分内容拿出来详细介绍一下。现在,假设你已经清楚什么是堆,什么是栈。

如果需要在堆上创建对象,要么使用new运算符,要么使用malloc系列函数。这点没有异议。

真正有异议的是下面的代码:

1
Object obj;

此时,obj是在栈上分配的吗?

要回答这个问题,我们首先要理解这个语句是什么意思。这个语句就是代表着,在栈上创建对象吗?

其实,这行语句的含义是,使对象obj具有“自动存储(automatic storage)”的性质。所谓“自动存储”,意思是这个对象的存储位置取决于其声明所在的上下文。

  • 如果这个语句出现在函数内部,那么它就在栈上创建对象。
  • 如果这个语句不是在函数内部,而是作为一个类的成员变量,则取决于这个类的对象是如何分配的。

考虑下面的代码:

1
2
3
4
5
6
class Class
{
Object obj;
};

Class *pClass = new Class;

指针pClass所指向的对象在堆上分配空间。因为Object obj;语句的含义是“自动存储”,所以,pClass->obj也是在堆上创建的。

理解了这一点,再来看下面的语句:

1
2
Object *pObj;
pObj = new Object;

Object *pObj;代表,指针pObj是自动存储的,仅此而已,没有任何其它含义。而下面一行语句则指出,这个指针所指向的对象是在堆上面分配的。如果这两行语句出现在一个函数内部,意味着当函数结束时,pObj会被销毁,但是它指向的对象不会。因此,为了继续使用这个对象,通常我们会在函数最后添加一个return语句,或者使用一个传出参数。否则的话,这个在堆上创建的对象就没有指针指向它,也就是说,这个对象造成了内存泄露。

并不是说指针指向的对象都是在堆上创建的。下面的代码则使用指针指向一个在栈上创建的对象:

1
2
Object obj;
Object *pObj = &obj;

至此,我们解释了函数内部的变量和成员变量。还有两类变量:全局变量和static变量。它们即不在堆上创建,也不在栈上创建。它们有自己的内存空间,是除堆和栈以外的数据区。也就是说,当Object obj即不在函数内部,又不是类的成员变量时,这个对象会在全局数据段创建,同理适用于static变量。对于指针Object *pObj;,如果这个语句出现在函数内部或类的成员变量,正如我们前面所说的,这个指针是自动存储的。但是,如果这个语句是在类的外部,它就是在全局数据段创建的。虽然它指向的对象可能在堆上创建,也可能在栈上创建。

堆和栈的区别在于两点:

  1. 生命周期
  2. 性能

第一点才是我们需要着重考虑的。由于栈的特性,如果你需要一个具有比其所在的上下文更长的生命周期的变量,只能在堆上创建它。所以,我们的推荐是:只要能在栈上创建对象,就在栈上创建;否则的话,如果你不得不需要更长的生命周期,只能选择堆上创建。这是由于在栈上的对象不需要我们手动管理内存。有经验的开发人员都会对内存管理感到头疼,我们就是要避免这种情况的发生。总的来说,我们更多推荐选择在栈上创建对象。

但是,有些情况,即便你在栈上创建了对象,它还是会占用堆的空间。考虑如下代码:

1
2
3
4
void func
{
std::vector v;
}

对象v是在栈上创建的。但是,STL 的vector类其实是在堆上面存储数据的(这点可以查看源代码)。因此,只有对象v本身是在栈上的,它所管理的数据(这些数据大多数时候都会远大于其本身的大小)还是保存在堆上。

关于第二点性能,有影响,不过一般可以忽略不计。确切的说,一般情况下你不需要考虑性能问题,除非它真的是一个问题。

首先,在堆上创建对象需要追踪内存的可用区域。这个算法是由操作系统提供,通常不会是常量时间的。当内存出现大量碎片,或者几乎用到 100% 内存时,这个过程会变得更久。与此相比,栈分配是常量时间的。其次,栈的大小是固定的,并且远小于堆的大小。所以,如果你需要分配很大的对象,或者很多很多小对象,一般而言,堆是更好的选择。如果你分配的对象大小超出栈的大小,通常会抛出一个异常。尽管很罕见,但是有时候也的确会发生。有关性能方面的问题,更多出现在嵌入式开发中:频繁地分配、释放内存可能造成碎片问题。

现代操作系统中,堆和栈都可以映射到虚拟内存中。在 32 位 Linux,我们可以把一个 2G 的数据放入堆中,而在 Mac OS 中,栈可能会限制为 65M。

总的来说,关于究竟在堆上,还是在栈上创建对象,首要考虑你所需要的生命周期。当性能真正成为瓶颈的时候,才去考虑性能的问题。堆和栈是提供给开发者的两个不同的工具,不存在一个放之四海而皆准的规则告诉你,一个对象必须放在堆中还是在栈中。选择权在开发者手中,决定权在开发者的经验中。

最后我们来补充个练习:

我们创建了一个Class Circle,这个Circle class 的头文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#ifndef INHERITANCE_CIRCLE_H
#define INHERITANCE_CIRCLE_H

class Circle {
public:
//class properties decleartion
double _r;
double _area;
//class function decleration
double getR() const;

void setR(double r);

double getArea() const;

void setArea(double area);

Circle();

~Circle();

Circle(double r);

Circle(double r, double area);
};
#endif //INHERITANCE_CIRCLE_H

Then’s let’s check the memory address to check we the pointer locates and where the real object locates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include "Circle.h"
// an object initialized on the dataspace
Circle CircleOnDataSpace(3, 10);

int main() {
std::cout << "Hello, memory!" << std::endl;
// an object initialized on the stack
Circle circle(3, 10);//circle locates on statck
Circle *ptrOnStack = &CircleOnDataSpace; // ptrOnStack is pointer and locates on stack
Circle *ptr = &circle;//pt locates on stack
// an object initialized on the heap
Circle * circle_ptr = new Circle(100,100); //new Circle creates an object locates on heap, but circle_ptr locates on stack

std::cout << "CircleOnDataSpace's address = " << (long)&CircleOnDataSpace << std::endl;
std::cout << "ptrOnStack's address = " << (long)&ptrOnStack << std::endl;
std::cout << "Circle's address = " << (long)ptr << std::endl;
std::cout << "Circle 1st member's address = " << (long)&(circle._r) << std::endl;
std::cout << "ptr address = " << (long)&ptr << std::endl;
std::cout << "circle_ptr's address = " << (long)&ptr << std::endl;
std::cout << "Circle's address = " << (long)circle_ptr << std::endl;
std::cout << "Circle's 1st member;s address = " << (long)&(circle_ptr->_r) << std::endl;
return 0;
}

The result is printed out as following:

CircleOnDataSpace’s address = 4344025352 //dataspace
ptrOnStack’s address = 140732871710304 //stack
Circle’s address = 140732871710312 //stack
Circle 1st member’s address = 140732871710312 //stack
ptr address = 140732871710296 //stack
circle_ptr’s address = 140732871710296 //stack
Circle’s address = 140471724682144 //heap
Circle’s 1st member;s address = 140471724682144 //heap

1