11. 堆和栈

堆(Heap)与栈(Stack)有两层含义:

  • 程序内存布局场景下,堆与栈表示两种内存管理方式

  • 数据结构场景下,堆与栈表示两种常用的数据结构

栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等,其操作方式类似于数据结构中的栈。

堆由程序员分配释放,若程序员不释放,程序结束时由系统回收。

11.1. 区别

管理方式

栈由操作系统自动分配释放,无需我们手动控制;

堆的申请和释放工作由程序员控制,容易产生内存泄漏。

空间大小

每个进程拥有的栈的大小要远远小于堆的大小。 理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小 64-bit 的 Windows 默认 1MB,64-bit 的 Linux 默认 10MB。

分配方式

堆都是动态分配的,没有静态分配的堆。栈有 2 种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。

动态分配由 alloc 函数进行分配,但是栈的动态分配和堆是不同的,其动态分配是由操作系统进行释放,无需我们手工实现。

生长方式

堆的生长方向向上,内存地址由低到高。

栈的生长方向向下,内存地址由高到低。

分配效率

栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。

堆则是由 C/C++ 提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多

存放内容

栈:存放函数返回地址、相关参数、局部变量和寄存器内容等。

堆:一般在堆的头部用一个字节存放堆的大小,堆中的具体内容由程序员安排。

11.2. 内存分区

在 C++ 中,内存主要分为堆、栈、全局/静态存储区和常量存储区。

  • :就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。

  • :就是那些由 new 分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个 new 就要对应一个 delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

  • 全局/静态存储区 :全局变量和静态变量被分配到同一块内存中,在以前的 C 语言中,全局变量又分为初始化的和未初始化的,在 C++ 里面没有这个区分了,他们共同占用同一块内存区。

  • 常量存储区 :这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。

11.3. 构造函数的空间分配

静态构造 :编译器为对象在栈空间中分配内存(直接移动栈顶指针,挪出适当的空间),然后在这片内存空间上调用构造函数构造一个栈对象。

A a;

动态构造 :在堆上申请内存;在堆内存上构造对象;指针指向该堆内存。

A* pa = new A();

限制在堆上构造对象

要求不能在栈空间构造类对象,直接将构造函数声明为 private 是不行的。因为 new 表达式实际上也调用了构造函数,会报错 error: 'A::A()' is private

在栈空间构造对象,是由编译器分配内存空间的。当对象被使用完之后,编译器会调用析构函数来释放栈对象所占的空间。编译器管理了对象的整个生命周期。如果编译器无法调用类的析构函数(比如,类的析构函数是 private),则编译器无法释放内存。所以,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性(其实不光是析构函数,只要是非静态的函数,编译器都会进行检查)。 如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存

另一方面,考虑到类的继承,应将构造函数和析构函数声明为 protected ,然后提供一个静态函数来完成对象的构造。

此外,还要方便释放对象所占用的内存空间。delete 表达式会调用析构函数,如果析构函数不是 public,在类外无法直接访问。

 1class A
 2{
 3protected:
 4    A(){}
 5    ~A(){}
 6public:
 7    static A* create()
 8    {
 9        return new A();
10    }
11    void destroy()
12    {
13        delete this;
14    }
15};
1A* pa = A::create();
2pa->destroy();

限制在栈上构造对象

将 new 和 delete 运算符重载,并声明为 private。

1class A
2{
3public:
4    A(){}
5    ~A(){}
6private:
7    void* operator new(size_t){}
8    void operator delete(void*){}
9};

11.4. 参考资料

  1. 堆与栈的区别

  1. C/C++——堆栈的讲解

  1. C++ 自由存储区是否等价于堆?

  1. 如何让类对象只在栈(堆)上分配空间?