内存那点事儿之“内存管理”——栈、堆、数据段(数据区)

问题:程序是在内存中运行的,是暂存一些临时变量的场所,这些内存是如何分配给数据的呢?也就是说这些数据是如何在内存中排布的呢?

首先要明白一个排布的工具:数据结构

数据结构:数据结构就是研究数据如何组织加工的
常见的数据结构有:数组(最简单的数据结构)、结构体、栈、堆、队列、链表、哈希表、二叉树…………

内存管理是由操作系统完成的,操作系统就是“包租婆”

(1)内存本质上是一个硬件
(2)程序使用内存:获取内存(在操作系统登记临时征用该内存的使用权限)、使用内存、释放内存(将内存归还给操作系统)。操作系统有点像“包租婆”,租房就是获取内存,入住就是使用内存,最后不住了将房子(内存)归还包租婆。

数组

数组管理内存和普通变量本质上没有太大差别

普通变量int a——首先,编译器在内存中申请一段内存(4字节),获得该内存的地址(编译器知道地址,我们程序员不知道地址是多少,也没必要知道);
然后将内存分配给a,再将数据变量名a与该地址绑定起来;
最后,等待a赋值,将赋值的数存入该内存。

数组int b[10]——首先,编译器在内存中申请一段大小为4×10=40字节的内存,并获得该内存的地址(实际是首地址);
然后,将内存分配给b,再将数组名b与首地址绑定;
最后,将赋值的数组内容存入内存

结构体

其实结构体就是更加“强大”的数组。因为在数组中有一个特点,也是数组的局限性——数组中元素的数据类型必须是相同的,也就是说同一个数组中不可能出现int和char类型。
所以,结构体作为“拯救者”出现了,它打破了这个局限性,在同一个结构体中可以出现多个不同的数据类型,如下;

struct studengt
{
	char name[];
	char sex[];
	int ages;
};

栈(stack)

栈是一种数据结构,在c语言中使用栈来管理普通局部变量(静态局部变量放在数据段上),是用来管理内存的。操作系统都会为每一个程序分配一段栈内存

栈管理内存的特点:先进后出(队列是先进先出)

C语言中的局部变量是如何利用栈实现的呢?
例如:在处于C语言中定义一个局部变量int a
首先,编译器会在栈中分配一段空间(4字节)给这个局部变量用(分配时栈顶指针会移动给出空间,给局部变量a用的意思就是,将这4字节的栈内存的内存地址和我们定义的局部变量名a给关联起来),对应栈的操作是入栈。
在这里插入图片描述

注意:这里栈指针的移动和内存分配是自动的(栈自己完成,不用我们写代码去操作)。
然后等我们函数退出的时候,局部变量要灭亡。对应栈的操作是弹栈(出栈)。出栈时也是栈顶指针移动将栈空间中与a关联的那4个字节空间释放(“”释放”可以理解为这个内存单元没有人住了,但是之前的租户没有把房子里面的东西收拾干净带走,很脏)。这个动作也是自动的,也不用人写代码干预。
在这里插入图片描述
上面的表达的意思是:a出栈后,内存仍然有a变量在,出栈相当于将a复制了一份拿出去,内存不变。
所以,我们在这里思考一下,虽然出栈使得内存得到了释放,但是内存中仍然“很脏”(没有清零),假如之前的int a=2,这个时候我们又要重新定义另外一个局部变量int d,但是我们没有将d初始化;将会发生什么意想不到的事情呢?
可能编译器正好将之前的int a地址内存分配给int b.这个时候b的值就是2。
经过上面的解释,我们就很容易明白在C语言中为什么变量一定要记住初始化了吧!嘿嘿

同时也可以理解这两段程序等价的原因:
int a; a=2; //等价于int a=2;

栈的优点:栈管理内存,好处是方便,分配和最后回收都不用程序员操心,C语言自动完成。

栈的局限性:栈的大小有限,不能定义太多局部变量,否则会出现栈溢出。例如:int d[100000]这种使用递归解决的问题一定要注意递归收敛,防止溢出风险!

堆(heap)

堆是由操作系统堆管理器管理,堆管理器是操作系统中的一个模块,管理内存方式;按需分配。

堆(heap)是一种内存管理方式。内存管理对操作系统来说是一件非常复杂的事情,因为首先内存容量很大,其次内存需求在时间和大小块上没有规律(操作系统上运行着的几十、几百、几千个进程随时都会申请或者释放内存,申请或者释放的内存块大小随意)。
堆这种内存管理方式特点就是自由(随时申请、释放;大小块随意)。堆内存是操作系统划归给堆管理器(操作系统中的一段代码,属于操作系统的内存管理单元)来管理的,然后向使用者(用户进程)提供API(malloc和free)来使用堆内存。

我们什么时候使用堆内存?
需要内存容量比较大时,需要反复使用及释放时,很多数据结构(譬如链表)的实现都要使用堆内存。

堆管理内存的特点(大块内存、手工分配&使用&释放):
特点一:容量很大(常规使用的需求容量都能满足)用多少申请多少
特点二:申请及释放都需要手工进行,手工进行的含义就是需要程序员写代码明确进行申请malloc及释放free
如果程序员申请内存并使用后未释放,这段内存就丢失了(在堆管理器的记录中,这段内存仍然属于你这个进程,但是进程自己又以为这段内存已经不用了,再用的时候又会去申请新的内存块,这就叫吃内存),这称为内存泄漏。在C/C++语言中,内存泄漏是最严重的程序bug,这也是别人认为Java/C#等语言比C/C++优秀的地方。
特点三:其实堆内存在malloc()之后和free()之前都是“”脏的”
C语言操作堆内存的接口(malloc、 free)
堆内存释放时最简单,直接调用free释放即可。
void free(void *ptr);
堆内存申请时,有3个可选择的类似功能的函数:
malloc, calloc, realloc
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);

  • nmemb个单元,每个单元size字节

void *realloc(void *ptr, size_t size);

  • 改变原来申请的空间的大小的

譬如要申请10个int元素的内存:
malloc(40); malloc(10*sizeof(int));
calloc(10, 4); calloc(10, sizeof(int));

数组定义时必须同时给出数组元素个数(数组大小),而且一旦定义再无法更改。在Java等高级语言中,有一些语法技巧可以更改数组大小,但其实这只是一种障眼法。它的工作原理是:先重新创建一个新的数组大小为要更改后的数组,然后将原数组的所有元素复制进新的数组,然后释放掉原数组,最后返回新的数组给用户;

堆内存申请时必须给定大小,然后一旦申请完成大小不变,如果要变只能通过realloc接口。realloc的实现原理类似于上面说的Java中的可变大小的数组的方式。
堆的优势和劣势(管理大块内存、灵活、容易内存泄漏)
优势:灵活;
劣势:需要程序员去处理各种细节,所以容易出错,严重依赖于程序员的水平

数据段(.data)

编译器在编译程序的时候会将程序分为代码段、数据段、bass段三个部分。
代码段:程序中可执行的部分,直观上理解就是函数堆叠而成的。
数据段;直观上理解就是C语言中的全局变量(注意:全局变量才算是程序的数据,而局部变量不算是程序的数据,只能算是函数的数据),并且全局变量初始化为非0.或者静态局部变量(static )也是放在数据段。
bass段:又叫ZI(zero initial)段。本质上也属于数据段,是全局变量初始化为0的数据段或者说是未显式初始化的全局变量(在c语言中未显式初始化的全局变量默认初始化为0)

注意:也有一些特殊的数据会被放到代码段,这里就不详细描述,后面将会专门写一篇博客来描述!

总结:内存(栈内存、堆内存、数据段)就是来存放变量的。栈内存存放普通局部变量(),堆内存存放大容量的局部变量,数据段(包括bass)存放全局变量和静态全局变量的。

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wu Junwu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值