基础知识篇——堆内存和栈内存
阅读原文时间:2021年04月20日阅读:1

文章目录

一、数据结构中的堆和栈

1. 栈

是一种连续储存的数据结构,具有先进后出的性质。

通常的操作有入栈(压栈),出栈和栈顶元素。想要读取栈中的某个元素,就是将其之间的所有元素出栈才能完成。

2. 堆

是一种非连续的树形储存数据结构,每个节点有一个值,整棵树是经过排序的。特点是根结点的值最小(或最大)且根结点的两个子树也是一个堆。常用来实现优先队列,存取随意。

二、内存中的栈区与堆区

1. 内存中的栈区与堆区比较

栈区

堆区

Stack memory内存空间由操作系统自动分配和释放。

Heap Memory内存空间手动申请和释放的,Heap Memory内存常用new关键字来分配

Stack Memory内存空间有限。

Heap Memor的空间是很大的自由区几乎没有空间限制。

2. 计算机内存的大致划分

一般说到内存,指的是计算机的随机存储器(RAM),程序都是在这里面运行。

三、栈内存与栈溢出

由程序自动向操作系统申请分配以及回收,速度快,使用方便,但程序员无法控制。若分配失败,则提示栈溢出错误。
注意,const局部变量也储存在栈区内,栈区向地址减小的方向增长。

    #include <iostream>
    int main()
    {
        int i = 10; //变量i储存在栈区中
        const int i2 = 20;
        int i3 = 30;
        std::cout << &i << " " << &i2 << " " << &i3 << std::endl;
        return 0;
    }


&i3 < &i2 < &i,证明地址是减小的。

四、堆内存与内存泄露

程序员向操作系统申请一块内存,当系统收到程序的申请时,会遍历一个记录空闲内存地址的链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序

  • 分配的速度较慢,地址不连续,容易碎片化。

  • 由程序员申请,同时也必须由程序员负责销毁,否则导致内存泄露。

    //测试堆内存和栈内存的区别
    #include
    int main()
    {
    int i = 10; //变量i储存在栈区中
    char pc[] = "hello!"; //储存在栈区
    const double cd = 99.2; //储存在栈区
    static long si = 99; //si储存在可读写区,专门用来储存全局变量和静态变量的内存
    int* pi = new int(100); //指针pi指向的内存是在堆区,专门储存程序运行时分配的内存
    std::cout << &i << " " << &pc << " " << &cd << " " << &si << " " << pi << std::endl;
    delete pi; //需程序员自己释放
    return 0;
    }

五、JAVA

1. Java中的堆与栈

在Java中:

  • 声明的对象是先在栈内存中为其分配地址空间,
  • 在对其进行实例化后则在堆内存中为其分配地址。

例如:
Person p = null ; 只在Stack Memory中为其分配地址空间
p = new Person(); 则在Heap Memory中为其分配内存地址


在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,

  • 当在一段代码块定义一个变量时,Java就在中为这个变量分配内存空间,
  • 当超过变量的作用域后,Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。

堆内存用来存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。

2. 引用变量

在堆中产生了一个数组或者对象之后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象,引用变量就相当于是为数组或者对象起的一个名称

引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。

而数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。

3. 堆和非堆内存

按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。 堆是在 Java 虚拟机启动时创建的。在JVM中,堆之外的内存称为非堆内存(Non-heap memory)”

可以看出JVM主要管理两种类型的内存:堆和非堆。
简单来说:

  • 堆就是Java代码可及的内存,是留给开发人员使用的;
  • 非堆就是JVM留给自己用的,所以
    1. 方法区、
    2. JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、
    3. 每个类结构(如运行时常数池、字段和方法数据)
    4. 方法和构造方法 的代码

都在非堆内存中。

六、《C++内存管理技术内幕》

1. C++中,内存分成5个区

根据《C++内存管理技术内幕》一书,在C++中,内存分成5个区,分别是:堆、栈、自由存储区、全局/静态存储区、常量存储区。

a)栈

内存由编译器在需要时自动分配和释放。通常用来存储局部变量和函数参数。

为运行函数而分配的局部变量、函数参数、返回地址等存放在栈区。

栈运算分配内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

b)堆

内存使用new进行分配,使用delete或delete[]释放。

如果未能对内存进行正确的释放,会造成内存泄漏

但在程序结束时,会由操作系统自动回收。

c)自由存储区

使用malloc进行分配,使用free进行回收。
和堆类似。

d)全局/静态存储区

全局变量和静态变量被分配到同一块内存中,C语言中区分初始化和未初始化的,C++中不再区分了。(全局变量、静态数据、常量存放在全局数据区)

e)常量存储区

存储常量,不允许被修改。

2. C++中,内存分成3个区

这里,在一些资料中是这样定义C++内存分配的,可编程内存在基本上分为这样的几大部分:静态存储区、堆区和栈区。他们的功能不同,对他们使用方式也就不同。

a)静态存储区

内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。

b)栈区

在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。

栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

c)堆区

亦称动态内存分配。

程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或 delete释放内存。

动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。

但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象

总结

C++与C语言的内存分配存在一些不同,但是整体上就一致的,不会影响程序分析。就C++而言,不管是5部分还是3大部分,只是分法不一致,将5部分中的c)d)e)合在一起则是3部分的a)。

下面几段代码,则会让你有豁然明白的感觉:

void fn(){ 
   int* p = new int[5];
}

看到new,首先应该想到,我们分配了一块堆内存,那么指针p呢?
  它分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针p。程序会先确定在堆中分配内存的大小,然后调用 operator new分配内存,然后返回这块内存的首地址,放入栈中
  
  注意:这里为了简单并没有释放内存,那么该怎么去释放呢? 是delete p么? NO,错了,应该是delete [ ]p,这是告诉编译器:删除的是一个数组。

//main.cpp 
int a = 0;   //全局初始化区 
char *p1;    //全局未初始化区 
main() 
{ 
int b;               //栈 
char s[] = "abc";    //栈 
char *p2;            //栈 
char *p3 = "123456"; //123456\0在常量区,p3在栈上。 
static int c =0;    // 全局(静态)初始化区 

// 分配得来得10和20字节的区域就在堆区。 
p1 = (char *)malloc(10); 
p2 = (char *)malloc(20); 
strcpy(p1, "123456"); // 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 
}

学习:
https://www.cnblogs.com/jiudianren/p/5671992.html
https://msd.misuland.com/pd/2884250137616452012