目录

内存分布

1.栈区:

2. 堆区:

3. 全局区(静态区static):

4. 文字常量区:

5. 程序代码区:

代码举例

二、不同区内存分配、管理方式

从静态存储区分配

在栈上创建

从堆上分配(动态内存分配)

三、堆和栈的区别

管理方式不同:

空间大小不同:

能否产生碎片:

生长方向不同:

分配方式不同:

分配效率不同:

四、几个问题的解释:

1 为什么栈比堆快?

2 关于堆栈的申请内存情况

3 关于函数调用的过程调用栈

4 关于递归的缺点


内存分布

以32位系统为例,共有4G的寻址能力,进程在内存中的分布如下图所示。Linux默认将高地址的1G空间分配给内核,称为内核空间,剩下的3G空间分配给进程使用,称为用户空间。

名称
操作系统内核区用户不可见
用户栈栈指针,向下扩展
动态堆向上扩展
全局区(静态区).data初始化.bss未初始化
文字常量区(只读数据)常量字符串
程序代码区栈指针,向下扩展

1.栈区:

  • 由编译器自动分配释放,速度较快
  • 用来存储函数调用时的临时信息的结构,存放为运行时函数分配的局部变量函数参数返回数据返回地址等。这些局部变量等空间都会被释放
  • 程序运行过程中函数调用时参数的传递也在栈上进行,如递归调用栈
  • 当栈过多的时候,就是导致栈溢出(比如大量的递归调用或者大量的内存分配)
  • 栈是向低地址扩展的数据结构,是一块连续的内存的区域,空间有限

2. 堆区:

  • 由程序员分配(new,malloc)释放(delete,free),并指明大小,速度较慢
  • 若程序员不释放,导致内存泄漏,new完没有delete
  • 不过在整个程序结束时由操作系统回收,但是这样无疑增加了操作系统的负担
  • 高地址扩展的数据结构,是不连续的内存区域,空间很大,比较灵活
  • 频繁地分配和释放不同大小的堆空间容易产生内存碎片

注:当堆区 数据地址 和 栈区数据地址相同时(碰面了) 就代表,数据已用完。

3. 全局区(静态区static):

  • 全局变量和静态变量是放在一起的
  • 初始化的全局变量和静态变量(static修饰的)放在一块区域.data节
  • 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域.bss(不占磁盘空间,不做具体解释)
  • 程序结束后由系统释放

4. 文字常量区:

  • 常量字符串就是放在这里,程序结束后由系统释放

5. 程序代码区:

  • 存放函数体的二进制代码

代码举例

//main.cpp 程序代码区
#define a 3 //代码区
int a = 0; //全局初始化区 
char *p1; //全局未初始化区 
main() 
{ 
    int b; //栈 
    char s[] = "abc"; //栈 
    char *p2; //栈 
    char *p3 = "123456"; //123456\0在常量区,p3在栈上。 
    static int c =0; //全局(静态)初始化区 
    p1 = (char *)malloc(10); 
    p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。 
    strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 
}

二、不同区内存分配、管理方式

从静态存储区分配

内存在程序编译的时候已经分配好,这块内存在程序的整个运行期间都存在。例如:全局变量,static变量。

在栈上创建

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

从堆上分配(动态内存分配)

程序在运行的时候使用malloc或者new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。

三、堆和栈的区别

管理方式不同:

是由编译器自动申请和释放空间是需要程序员手动申请和释放

空间大小不同:

的空间是有限的,在32位平台下,VC6下默认为1M最大可以到4G

能否产生碎片:

和数据结构中的栈原理相同,在弹出一个元素之前,上一个已经弹出了,不会产生碎片,如果不停地调用malloc、free会造成内存碎片很多;

生长方向不同:

,向着内存减小的方向生长,生长方向是向上的,是向着内存地址增加的方向。

分配方式不同:

静态分配动态分配静态分配编译器完成的,比如局部变量的分配。动态分配由 malloc 函数进行分配。
都是动态分配的,没有静态分配的堆。

分配效率不同:

栈的效率比堆高很多。
机器系统提供的数据结构,计算机在底层提供栈的支持,分配专门的寄存器来存放栈的地址,压栈出栈都有相应的指令,因此比较快
是由库函数提供的,机制很复杂,库函数会按照一定的算法进行搜索内存,因此比较慢。

四、几个问题的解释:

1 为什么栈比堆快?

栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new/malloc分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。
而且
栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放
堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定

2 关于堆栈的申请内存情况

只要栈的剩余空间大于所申请空间,系统将为程序分配内存,否则将报异常提示栈溢出。对于堆,操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中

3 关于函数调用的过程调用栈

栈为执行的线程留出了内存空间,当函数被调用的时候,会有一个过程调用栈,栈内保存函数调用的返回地址,被调用函数的入口参数局部变量等信息,调用结束后,这些信息被释放,然后栈顶指针继续指到返回地址的位置。所以每个线程都有自己的栈,那么栈空间就是私有的。线程结束的时候栈会被回收。一个简单的调用例子,主函数调用了caller,caler又调用了add

4 关于递归的缺点

递归虽然简洁,但是也有很多缺点
递归是由于函数调用自身,函数调用本身是有时间和空间消耗,每一次函数调用都需要往内存栈分配空间,需要保存现场信息,(返回地址,以方便调用之后返回)局部变量,入口参数,有时还需要调用者保存寄存器等,入栈和出栈都需要时间,所以递归的效率很慢

第二个问题是栈溢出:每一次函数调用都会在内存栈分配空间,每个进程的栈容量有限,当递归调用的层级太多,就会超出栈容量,造成栈溢出。linux的栈一般是8M

总结:当你编译之前知道分配数据的大小,并且不是太大的时候,推荐用栈,当在运行期间不知道会需要多大的数据或者需要分配大量内存,建议用堆

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐