指针高级应用——动态内存管理

种一棵树最好的时间是十年前,其次是现在!

废话不多说,进入正题!

学习数组的时候我们只能先定义一个定长的数组,但是实际情况呢?我们可能因为需求的原因以至于这个数组不够长。(c99之前数组的长度事先指定只能是一个常量)。C语言指出动态内存分配。利用动态内存分配,我们就可以设计出根据需求扩大或缩小的数据结构。

一、动态内存分配函数

这边有三种内存分配函数。他们是malloc、calloc、realloc三函数。

malloc——分配内存块,但是不对内存块进行初始化

calloc——分配内存块,并且对内存块进行清零

realloc——调整先前分配的内存块大小

malloc的函数原型:void *malloc(size_t size)

我们最常用的就是malloc函数。因为malloc函数不需要对分配的内存块进行清零,所以它比calloc函数更高效(刚才不小心把 高效 打成了 搞笑 二字,哈哈真搞笑,嗯,我们继续。)

1.malloc返回值类型:

由于不知道我们计划储存在内存块中的数据是什么类型的,所以它不返回int 不返回 char等类型的普通指针,它返回 void * 类型的指针。void * 类型指 “通用型”指针。

注意:不要对void *类型的指针进行运算操作,比如说指针运算、指针移动等,因为我们不知道它是什么类型的指针 + 1不知道移动几个字节长度。

2.malloc的参数:

malloc函数分配size个字节内存块,并且返回指向该内存块的指针。注意:size的类型是无符号整数类型,通常我们就只把它当做普通整数(除非正在分配一个非常巨大的内存块(大佬说的/dog))。

3.空指针:

内存空间是有限的,当调用内存分配函数时,内存空间被用完了,找不到足够大的内存块了,此时就会函数返回一个空指针null。空指针是不指向任何地方的指针。

所以我们在吧函数返回值储存到指针变量中后,需要判断该指针变量是否为空指针。如果是,就采取适当措施(通常会使用printf提示一些信息并exit(1)异常退出/退回上一层调用,这里不多说了)。

二、释放存储空间

1.我们申请的动态内存是需要我们手动进行释放。如果总是malloc申请了一个又一个的内存块却没有释放,会导致 堆 的耗尽(malloc等内存分配函数所获得的内存块来自一个名叫 堆 的存储池),这样就会导致函数返回空指针。

甚至,程序可能分配了内存块又丢失了对这些块的记录,因而浪费了空间,比如:

q = malloc(...);
p = malloc(...);
p = q;

p、q都分别指向了不同的内存块(我们称内存块P和内存块Q),而p = q就把q的地址给了p,让p也指向了内存块Q,这样一来内存块P就成了没有记录的内存块,虽然申请了但已经丢失了记录。这样不可再访问到的内存块称为垃圾,而C语言并没有垃圾回收机制,意味着我们需要自己手动去回收这些内存!

2.方法便是调用free函数来释放不必要的内存。

free函数在<stdlib.h>的原型:

void free(void *ptr);

功 能: 释放ptr指向的存储空间。被释放的空间通常被送入可用存储区池,以后可在调用malloc、realloc以及calloc函数来再分配。

只需要简单地把指向不再需要的内存块的指针传递给free函数即可:

int *p;
int n = 5;
p = malloc(sizeof(int) * n);
free(p);

free释放了p所指向的内存块,之后此内存块就可以被后续的malloc函数或者其他内存分配函数的调用重新使用。注意:free的参数必须是之前由内存分配函数返回的指针!

3.悬空指针

free(p)函数会释放掉p指向的内存块,但是并不会改变p本身,如果忘记了p已经不再指向有效的内存块,那么就会出现问题。有这样的情况,几个指针同时指向同一内存块,而释放掉此内存块之后,全部的指针都就悬空了。

试图修改或者访问释放掉的内存块会导致未定义的行为,这就是悬空指针潜在的风险——在我们不知道的情况下就访问或者修改了已经释放掉的内存块而导致严重后果。

三、动态内存分配数组

前面已经说过了,我们写程序时很难估算数组的大小,而我们现在已经可以解决它了——动态内存分配数组。在程序执行期间为数组分配空间,然后通过指向数组的第一个元素的指针来访问数组。

使用malloc函数为数组分配储存空间

假设正在编写的程序需要一个n个整数构成的数组,n在我们程序运行的期间计算出来了,我们需要使用sizeof运算符来计算出每个元素所需要的空间 ,然后乘以n作为malloc函数的参数来为这个数组分配存储空间。(计算数组所需空间数量时始终要使用sizeof函数!因为可能系统的不同,一个int型整数所占的字节不同,而sizeof就可以提高代码的可移植性。)

一旦a指向了动态分配的内存块,我们就可以忽略掉a是指针的事实,把它当做数组的名字,然后嘛,你想对这个“数组”做什么就做什么吧!

 

 

有什么错误还请指出,谢谢~~

 

猜你喜欢

转载自blog.csdn.net/qq_51182221/article/details/115145224