内存四区

定义

内存四区包含静态区、代码区、堆区、栈区

静态区

全局变量和静态变量存储在静态区

堆区

malloc的变量放在堆区,堆区一般由程序员分配释放。若程序员不释放,程序结束时可由操作系统回收。其分配方式类似于链表,在内存中的分布不是连续的,它们是不同区域的内存块通过指针链接起来的。一旦某一节点从链中断开,我们就要人为的将断开的节点从内存中释放。

  • 堆的增长方式
    由低地址向高地址

    栈区

局部变量存在栈区,由编译器自动分配释放,存放函数的参数值、局部变量的值。其操作方式类似数组,它的内存分配是连续分配的,即分配的内存是一块连续的内存区域。当声明变量时,编译器会自动接着当前栈区的结尾继续分配内存。

  • 栈的增长方式
    由高地址向低地址,函数参数载入时由右向左

    常量区

常量字符串存放于此,程序结束后由系统自动释放

代码区

存放函数体的二进制代码
一个正常的程序在内存中通常分为程序段、数据段和堆栈三部分。程序段放着程序的机器码、只读数据。这个段通常是只读,对它的写操作是非法的。数据段放的是程序中的静态数据,动态数据则通过堆栈来存放。

内存低端——-
程序段
数据段
堆 栈
内存高端——-

堆栈是内存中的一个连续的块,一个叫堆栈指针的寄存器(SP)指向堆栈的栈顶,堆栈的底部是一个固定地址。堆栈有一个特点就是,后进先出。也就是说,后放入的数据第一个取出。它支持两个操作,PUSH和POP。PUSH是将数据放到栈的顶端,POP是将栈顶的数据取出。

在高级语言中,程序函数调用、函数中的临时变量都用到堆栈。为什么呢?因为在调用一个函数时,我们需要对当前的操作进行保护,也为了函数执行后,程序可以正确的找到地方继续执行,所以参数的传递和返回值也用到了堆栈。

通常对局部变量的引用是通过给出它们对SP的偏移量来实现的,另外还有一个基址指针(FP,在Intel芯片中是BP),许多编译器实际上是用它来引用本地变量和参数的。
通常,参数的相对FP的偏移是正的,局部变量是负的。

当程序中发生函数调用时,计算机做如下操作:首先把参数压入堆栈;然后保存指令寄存器(IP)中的内容,做为返回地址(RET);第三个放入堆栈的是基址寄存器(FP);然后把当前的栈指针(SP)拷贝到FP,做为新的基地址;最后为本地变量留出一定空间,把SP减去适当的数值。

堆和栈的基础知识

申请方式


  • 系统自动分配,声明局部变量int b;
    系统自动在栈中为b分配空间

  • 程序员自己申请,并指明大小,在C中使用malloc函数,如p1 = (char *)malloc(10);
    在C++中使用new运算符。但是,p1和p2本身是在栈中存储的。

    申请后系统的响应


  • 只要栈的剩余空间大于所申请的空间,系统就为程序提供内存,否则将异常提示栈溢出

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

    申请大小的限制


  • 在Windows中,栈是向低地址扩展的数据结构,是一块连续的内存区域,即栈顶的地址和栈的最大容量是系统预先规定好的。在Windows下,栈的大小是2M(它是编译时就确定的常数)。若申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

  • 堆是向高地址扩展的数据结构,是不连续的内存区域。由于系统是用链表来存储空闲的内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆得大小受限于计算机系统中有效的虚拟内存。由此可知,堆获得的空间比较灵活,也比较大。

    申请效率的比较


  • 由系统自动分配,速度较快,但程序员无法控制。

  • 用户由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来方便。

在Windows下,最好的方式是用VirtualAlloc分配内存,它不在堆,也不在栈,是直接在进程的地址空间中保留一块内存。虽然用起来最不方便,但是速度快,也最灵活。

堆和栈的存储内容



  • 在函数调用时,第一个进栈的是主函数中被调函数的下一条可执行语句的地址。然后是函数的各个参数,在大多数C编译器中,参数是由右往左入栈的,接着是函数中的局部变量。其中,静态变量是不入栈的。
    当本次函数调用结束后,局部变量先出栈,然后是函数参数,最后是栈顶指针指向最开始存的地址,即主函数中的下一条指令,程序由该点继续往下执行。

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

    存取效率的比较


1
2
char s1[] = "aaaaaaaa";
char *s2 = "bbbbb";

aaaaaaaa是在运行时进行赋值的,bbbbb是在编译时就确定了的。但是,在以后的存取中,在栈上的数组比指针所指向的字符串(如堆)快。

1
2
3
4
5
6
7
8
9
#include<stdio.h>
int main(){
char a = 1;
char c[] = "123456";
char *p = "123456";
a = c[1];
a = p[1];
return 0;
}

对应的汇编:

1
2
3
4
5
6
7
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al

第一种在读取时直接将字符串中的元素读到寄存器cl中,而第二种则先把指针值读取到edx中,再根据edx读取字符串。

总结

  • 堆和栈的区别:
    使用栈就如去饭店吃饭,只需点菜(申请内存),付钱并吃(使用),不必做扫尾等工作,好处是快捷,但自由度小;
    使用堆就如自己做菜,做自己想吃的,自由行大;
    这里的堆实际指满足堆性质的优先队列的一种数据结构,第一个元素有最高的优先权;栈实际上是满足先进后出的性质的数据结构。