找回密码
 注册
关于网站域名变更的通知
查看: 391|回复: 3
打印 上一主题 下一主题

堆内存的原理和使用

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2021-10-13 10:55 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
在一般的编译系统中,堆内存的分配方向和栈内存是相反的。当栈内存从高地址向低地址增长的时候,堆内存从低地址向高地址分配。
5 m4 I- U, K( Q
; w1 A" `+ l/ t& Q8 W/ Z在C语言中,堆内存在分配和释放的时候,是程序通过调用C语言的库函数完成的。这和栈内存的分配有区别,栈内存利用的是处理器的硬件机制,而堆内存的处理使用的是库函数。
' U, N: V- K3 \: P- q) p' Z6 K1 a% n
! x6 a, ?/ l* v9 r1 g7 V0 {" G
我们来看下堆内存的分配情况:
  L2 z0 T# E* ]( g; ]: E2 I8 q
* `" c! Z. c5 N. S3 i7 w; y/ G6 G$ J
在堆内存的分配过程中,每次分配将返回一个当前分配地址的指针。在程序中如果多次分配内存,可以得到多个内存指针,每个内存指针都是本次分配内存的地址。在释放内存的时候,只需要对每个指针进行操作,那个指针所指向的内存就会被释放,而对其他的内存区域没有影响。
; Y, ?9 Z' e2 P+ v$ d# Q6 P0 b- {0 S* e9 x. d! L/ W. J
从内存的分配和使用上,可以看出栈内存和堆内存的区别:栈内存只有一个入口点,就是栈指针,栈内存压入和弹出的时候栈指针将发生变化,栈指针标识当前栈区域中已使用和未使用的界限,程序在访问栈内存的时候都只能通过栈指针及其偏移量;而堆内存有多个入口点,每次分配得到的指针是访问内存的入口,每个分配内存区域都可以被单独释放,程序对堆内存可以通过每次分配得到的指针访问。; L0 Z' o# ~% X  N2 B5 Z: S2 k! ~8 a
  p) x% X; J& r2 V
堆内存有一个整体分配的过程,按照向上的堆内存分配方向。随着堆内存使用量的增加,堆内存将逐渐向高地址分配。这只是一个大体的增长的方面,在堆内存中,已使用的区域和未使用的区域是交错的,而不是像栈区域那样有明显的分界线。$ d) \1 v; a* f1 Q; e

: A6 K1 t4 X4 ], ]# ]3 Z
7 ]6 |3 r: j/ c* X5 m3 d5 y堆内存的释放看下面这个图:& P1 s& f! ~; F8 e

" m9 [5 I9 b2 l( Z, L" y. p# P看到这样频繁的使用区域和释放,那么很容易看出堆内存是不连续的,跟堆内存的使用方式有关系,这个分配就相对自由灵活了,但是也是会在低地址向高地址发展的方向分配的。: l* X, ~  u7 ?( }3 N3 _, ]" x# k
1 O2 j4 C; O0 j
比如上面释放后再分配就可以是下面两种情况:
; x3 c8 D3 t  x$ G' ?4 U4 H7 z
2 |% V! I# m4 N8 e, c5 j/ X$ {
先看再次分配1的情况:当新分配的需求比中间(刚刚释放)区域小,那么就会在紧接着的区域给分配。
% n- ^9 t2 X# O0 C2 f
再看再次分配2的情况:当新分配的需求比中间(释放的)区域大,那么只能往后寻求能给的区域。

1 B$ W: f: ?+ e1 S# P1 M. {# u
当频繁的分配和释放内存的过程中,会很容易出现在两块已经分配的内存之间较小的未分配内存区域,这些其实可以用,但是由于他们的空间比较小,不够连续内存的分配,所以分配的时候就很难再次使用,这些较小的内存就是我们常说的内存碎片。
: D$ D! R5 Y/ N" L4 e0 ^
我们再来聊一下在C程序中堆空间的使用。
( ]* V5 y" r+ [$ Z3 D! V; ^
在C语言中,堆内存区域的分配和释放是通过调用库函数来完成的,实现的函数主要有四个:
void *malloc(size_t size);    //分配内存空间
void free(void *ptr);     //释放内存空间
void *calloc(size_t nmemb,size_t size);  //分配内存空间
void *realloc(void * ptr,size_t size);  //重新分配内存空间
注意:使用上面这几个函数需要包含标准库文件 <stdlib.h>
那么库函数怎么使用呢,内存分配了就要有释放,那么常用的就是malloc()和free()两个函数。malloc()函数的输入是需要分配内存的大小,输出是分配内存的指针。如果分配不成功,则返回NULL。

; B" |5 Q* S- C0 b
free()函数的输入是需要释放的指针,可以接受任何形式的指针。这个指针必须是由分配函数分配出来的。

1 p* m. B5 v; j+ r# b8 g
例如:
int *pa;
pa = (int *)malloc(sizeof(int));//分配一个int大小的指针
if(NULL != pa)
{
free(pa);
}
内存使用完成需要释放,以便分配给其他程序使用。
calloc()也是内存分配的,只是可以把分配好的内存区域的初始值全部设置为0。还有这个分配内存有两个参数,第一个是分配单元的大小,第二个是要分配的数目。
malloc(sizeof(unsigned int)*10);   ==     calloc(sizeof(unsigned int),10)
realloc()有两个参数,一个是指向内存的地址指针,一个是要重分配内存的大小,返回值是指向所分配内存的指针。
/ b, h0 n5 z5 _- \( b
1、当参数指针为NULL的时候,作为malloc使用,分配内存。
2、当重分配内存大小为0的时候,作为free使用,释放内存。
3、当指针和重分配内存大小均不为0的时候,根据指针指向的堆内存区域的情况和指针大小重新分配内存。

6 x+ ]/ w! d$ u% L$ ]7 L+ Z
对于realloc()作为重新分配内存的时候,有三种可能出现:
1、缩小内存
2、扩大内存,不需要移动指针
3、扩大内存,需要移动指针(指定内存区域大小不够)

$ t. C) f# g8 C! K8 g1 R1 ^' U
在堆内存的管理上,主要容易出现以下几个问题:
1、开辟的内存没有释放,造成内存泄漏(系统不会释放任何用户分配的内存)
2、野指针被使用或释放(内存释放后,需要将内存指针置为NULL)
3、非法释放指针(分配了有效内存才存在释放,否则是非法的)
( H- t$ ]# m) K/ m
在C语言语法的方面对栈内存和堆内存如何使用没有限制。然后从使用的角度,栈内存更适用于容量较小的单个变量(例如:C语言的基本变量类型、较小的结构体和数组),堆内存则适用于开辟较大块的内存。栈内存由编译器分配和释放,堆内存由程序员分配和释放。

, s7 _0 `* g( H' v+ u

6 J, m6 d. C, k! U
- }! c. H6 Z( q  j6 ?3 h' l  Q+ I3 ?$ x7 s3 E: g. v9 c# E
# D( G% u! g* u* y) ^+ m' V

6 T; A  \0 R. K3 A9 u* \6 p+ Y8 H, W" }6 t* S2 ^2 Z6 N

; Z: O1 P- R1 ~) E6 S7 T8 l4 v3 V  K5 j& A5 m3 B* Q3 E

该用户从未签到

2#
发表于 2021-10-13 13:07 | 只看该作者
栈内存只有一个入口点,就是栈指针

该用户从未签到

3#
发表于 2021-10-13 14:30 | 只看该作者
在一般的编译系统中,堆内存的分配方向和栈内存是相反的

该用户从未签到

4#
发表于 2021-10-13 14:30 | 只看该作者
堆内存在分配和释放的时候,是程序通过调用C语言的库函数完成的
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-10-12 17:26 , Processed in 0.156250 second(s), 23 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表