vlambda博客
学习文章列表

C语言中内存的管理与使用—堆与栈

内存管理是计算机学习编程的一个重要知识,也是令大多数程序员比较头疼的一个知识。由于在目前的嵌入式系统中资源仍然是有限的,所以对内存的管理就显得尤为重要。C语言程序的内存接口简单,内存管理灵活,所以是初学者容易出错的知识,下面就让我们分三篇文章进行讲解,这篇文章主要讲解的是内存管理中的堆与栈。

一、堆与栈特点

栈:由系统自动分配的释放,用来存放函数的参数、局部变量的值。有先进先出的特点

堆:由程序员分配释放,若程序员为进行释放会由OS进行回收。

C语言中内存的管理与使用—堆与栈

二、什么是堆?

通过上面的两句话只是简单的概括了一下堆栈的特点,并不能解释什么是堆和栈,下面就让我们详细的看一下C语言中的堆是如何解释的。

一个为计算机程序可以分为两个部分:存放代码的代码段和存放变量和数据的数据段,数据段中又增加了全局初始化数据区和未初始化数据区。其中全局初始化数据区包含程序中明确被初始化的全局变量和静态变量以及常量。

其中堆是一个位于未初始化数据区(BSS)段和栈之间,用来动态分配内存。这段区域由程序员管理,程序员利用操作系统提供的分配和释放函数可以使用堆区的内存,每个程序员会进行扫描未用空间,当一个空间的大小符合申请空间时,就会将此空间返回给程序员,同时把申请加入到链表,操作系统就是通过此链表来维护一个堆的使用。

为避免程序员频繁申请小的堆,我们在申请的时候需要遵守以下几点:

1、用户需要自己分配内存在堆区,便于用户管理内存以及操作系统监控

2、临时数据放在栈区,生命周期短

3、全局数据和静态数据在程序中都能被访问,因此单独储存管理

4、程序运行是按顺序进行的,虽有跳转,数据需要多次访问,开辟单独的数据空间方便数据的访问与分分类。

三、什么是栈?

栈是由编译器自己分配和释放空间的一个区域,用来存储函数的参数、局部变量等。当函数被调用时,被调用的函数参数和返回值被储存在当前函数栈区之后调用函数再为自身的自动变量和临时变量在栈区分配空间。当函数调用返回时,在栈区内的参数返回值、自动变量和临时变量等会被自动释放。

函数的调用和栈的使用方式保证了函数内部定义相同名字的变量不会混淆。栈的管理方式为先进先出,学过C语言的应该知道这个特点。

四、实例中的堆与栈

在实际编程过程中,可以通过两种形式来实现:数组的形式实现栈,称为静态栈;以链表的形式来实现栈,称为动态栈;

相对于栈“先进先出”的特点,堆则为一种经过排序后形成树形数据结构,常用来实现优先队列等。由此可见堆是一种特殊的二叉树。其中节点从左到右填满,并且最后一个树叶在最左边。

由此可见,内存分配的堆栈与数据结构中所阐述的堆栈有着本质的区别,这一点千万不要混淆。同样,在内存分配中的堆和栈也存在着很大的区别,也不要混淆这两者的概念。为了加深理解,看下面一段示例代码:

#include <stdio.h>
#include <malloc.h>
int main(void)
{
/*在栈上分配*/
int i1=0;
int i2=0;
int i3=0;
int i4=0;
printf("栈:向下\n");
printf("i1=0x%08x\n",&i1);
printf("i2=0x%08x\n",&i2);
printf("i3=0x%08x\n",&i3);
printf("i4=0x%08x\n\n",&i4);
printf("--------------------\n\n");
/*在堆上分配*/
char *p1 = (char *)malloc(4);
char *p2 = (char *)malloc(4);
char *p3 = (char *)malloc(4);
char *p4 = (char *)malloc(4);
printf("p1=0x%08x\n",p1);
printf("p2=0x%08x\n",p2);
printf("p3=0x%08x\n",p3);
printf("p4=0x%08x\n",p4);
printf("堆:向上\n\n");
/*释放堆内存*/
free(p1);
p1=NULL;
free(p2);
p2=NULL;
free(p3);
p3=NULL;
free(p4);
p4=NULL;
return 0;
}



该示例代码主要演示了在内存分配中的堆和栈的区别,其运行结果为:

i1=0x0060fefc
i2=0x0060fef8
i3=0x0060fef4
i4=0x0060fef0
--------------------
p1=0x00bd14e0
p2=0x00bd3148
p3=0x00bd3158
p4=0x00bd3168

五、内存中的堆与栈

在C语言中,内存分配的方式一般有以下三种,分别是静态存储区域分配;在栈上分配;从栈上分配;

从静态存储区分配:由编译器自动分配和释放的,在程序的整个运行期间都存在,直到整个程序运行结束时才被释放,比如常见的全局变量与 static 变量。

在栈上分配:它同样也是由编译器自动分配和释放的,即在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元将被自动释放。需要注意的是,栈内存分配运算内置于处理器的指令集中,它的运行效率一般很高,但是分配的内存容量有限。

从堆上分配:也被称为动态内存分配,它是由程序员手动完成申请和释放的。即程序在运行的时候由程序员使用内存分配函数(如 malloc 函数)来申请任意多少的内存,使用完之后再由程序员自己负责使用内存释放函数(如 free 函数)来释放内存。

也就是说,动态内存的整个生存期是由程序员自己决定的,使用非常灵活。需要注意的是,如果在堆上分配了内存空间,就必须及时释放它,否则将会导致运行的程序出现内存泄漏等错误。

六、堆与栈深度讲解

栈和堆的分配方式的差别造成对栈内存的自动释放而言,虽然堆上的数据只要程序员不释放空间就可以一直访问,但是,如果一旦忘记了释放堆内存,那么将会造成内存泄漏,导致程序出现致命的潜在错误。

对堆来说,频繁分配和释放(malloc / free)不同大小的堆空间势必会造成内存空间的不连续,从而造成大量碎片,导致程序效率降低;而对栈来讲,则不会存在这个问题。

栈是机器系统提供的数据结构,计算机会在底层对栈提供支持,而堆则不同,它是由 C/C++ 函数库提供的,它的机制也相当复杂。很显然,堆的分配效率比栈要低得多。

七、变量类型

最后介绍一下 C 语言中各类型变量的存储位置和作用域。

全局变量:从静态存储区域分配,其作用域是全局作用域,也就是整个程序的生命周期内都可以使用。与此同时,如果程序是由多个源文件构成的,那么全局变量只要在一个文件中定义,就可以在其他所有的文件中使用,但必须在其他文件中通过使用extern关键字来声明该全局变量。

全局静态变量:从静态存储区域分配,其生命周期也是与整个程序同在的,从程序开始到结束一直起作用。但是与全局变量不同的是,全局静态变量作用域只在定义它的一个源文件内,其他源文件不能使用。

局部变量:从栈上分配,其作用域只是在局部函数内,在定义该变量的函数内,只要出了该函数,该局部变量就不再起作用,该变量的生命周期也只是和该函数同在。

局部静态变量:从静态存储区域分配,其在第一次初始化后就一直存在直到程序结束,该变量的特点是其作用域只在定义它的函数内可见,出了该函数就不可见了。

往期回顾



01


02


03


04