C语言中的堆与栈20160604

时间:2023-09-21 09:52:38

首先声明这里说的是C语言中的堆与栈,并不是数据结构中的!
一、前言介绍:
C语言程序经过编译连接后形成编译、连接后形成的二进制映像文件是静态区域由代码段和数据段(由二部分部分组成:只读数据

段,未初始化数据段即BBS(属于静态区域但不占空间,而且一般编译器会置零所以一般还是在内存区也就是动态区))组成,

运行是使用的内存是在动态区域,由已经初始化读写数据段,堆和栈组成

如下图所示:

C语言中的堆与栈20160604
其中所谓静态,就是一定会存在的而且会永恒存在、不会消失,这样的数据包括常量、常变量(const 变量)、静态变

量、全局变量等。

动态的话,就是会变化的了。动态的区域,就是堆和栈。这个栈应该称作 call stack,上面会存放函数的返回地址、

参数和局部变量。而堆放就是我们通过 new 算符和 malloc 函数分配得到的空间。
具体的:
1.栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量等值。局部变量,任务线程 函数之类的是放在

(使用)栈里面的,栈利用率高一些。其操作方式类似于数据结构中的栈。特别,栈是属于线程的,每一个线程会有一

个自己的栈
2.堆区(heap):一般由程序员分配释放,若程序员不释放,则可能会引起内存泄漏。注意它和数据结构中的堆是两回事

,分配方式倒是类似于链表,常见的就是malloc出来的都是属于堆区,就像固定出来的区域,到free的时候才释放,有

点类似全局的,静态的。
3.程序代码区:存放函数体的二进制代码。
4.数据段:由三部分组成:
1>只读数据段:
只读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表式的操作,由于这些变量不需要更改,

因此只需要放置在只读存储器中即可。一般是const修饰的变量以及程序中使用的文字常量一般会存放在只读数据段中


2>已初始化的读写数据段:
已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器(内存)的空间,在程序执行时它们需要位于可

读写的内存区域内,并且有初值,以供程序运行时读写。在程序中一般为已经初始化的全局变量,已经初始化的静态局

部变量(static修饰的已经初始化的变量)
3>未初始化段(BSS):
未初始化数据是在程序中声明,但是没有初始化的变量,这些变量在程序运行之前不需要占用存储器(内存)的空间。与读写数

据段类似,它也属于静态数据区。但是该段中数据没有经过初始化。未初始化数据段只有在运行的初始化阶段才会产生

,因此它的大小不会影响目标文件的大小,而且一般编译器还会自动初始化清零所以这时就在内存区了。

在程序中一般是没有初始化的全局变量和没有初始化的静态局部变量。
二、两者的区别:
1.申请方式
(1)栈(satck):由系统自动分配。例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间。
(2)堆(heap):需程序员自己申请(调用malloc,realloc,calloc),并指明大小,并由程序员进行释放。容易产生

memory leak.
eg:char p;
p = (char *)malloc(sizeof(char));
但是,p本身是在栈中。
在C++中用new运算符
如p2 = new char[10];
但是注意p1、p2本身是在栈中的。
2.申请大小的限制
在内存中分配位置,跟硬件架构和操作系统都有关系,x86中栈都是由高地址向低地址分配,堆是由低地址向高地址分

配,不过在 Windows 和 Linux 中堆和栈的位置相反
(1)栈:在windows下栈是向底地址扩展的数据结构,是一块连续的内存区域(它的生长方向与内存的生长方向相反)。

栈的大小是固定的。如果申请的空间超过栈的剩余空间时,将提示overflow。这句话的意思是栈顶的地址和栈的最大容

量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请

的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
(2)堆:堆是高地址扩展的数据结构(它的生长方向与内存的生长方向相同),是不连续的内存区域。这是由于系统

使用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由底地址向高地址。堆的大小受限于计算机系

统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
3.系统响应:
(1)栈:只要栈的空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
(2)堆:首先应该知道操作系统有一个记录空闲内存地址的链表,但系统收到程序的申请时,会遍历该链表,寻找第

一个空间大于所申请空间的堆结点,然后将该结点从空闲链表中删除,并将该结点的空间分配给程序,另外,对于大多

数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的free语句才能正确的释放本内存空间。

另外,找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
说明:对于堆来讲,对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率

降低。对于栈来讲,则不会存在这个问题,
4.申请效率
(1)栈由系统自动分配,速度快。但程序员是无法控制的
(2)堆是由malloc分配的内存,一般速度比较慢,而且容易产生碎片,不过用起来最方便。
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保

留一块内存,虽然用起来最不方便。但是速度快,也最灵活。
5.堆和栈中的存储内容
(1)栈:在函数调用时,第一个进栈的主函数中后的下一条语句的地址,然后是函数的各个参数,参数是从右往左入

栈的,然后是函数中的局部变量。注:静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条

指令,程序由该点继续执行。
(2)堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。
6.存取效率
(1)堆:char *s1=”hellow tigerjibo”;是在编译是就确定的
(2)栈:char s1[]=”hellow tigerjibo”;是在运行时赋值的;
但是,在以后的存取中,在栈上的数组(可变,局部)比指针所指向的字符串(例如堆,不可变,静态)快 ,指针在底层汇编中需要用edx寄存器中转一

下,而数组在栈上读取。
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
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读取字符,显然慢了。
补充:
栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的

指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,

库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没

有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会

分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
7.分配方式:
(1)堆都是动态分配的,没有静态分配的堆。
(2)栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca

函数进行分配,但是栈的动态分配和堆是不同的。它的动态分配是由编译器进行释放,无需手工实现。

小结:

第一,堆是malloc动态分配出来的,类似全局的,静态的(不可变的),栈是编译器完成的,主要存放是线程,函数,参数,局部变量(可变的)

第二,堆的效率低,但使用方便,栈的效率高,但程序猿无法控制

堆和栈的区别也可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就
走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自
由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且*
度大。