当前位置: 首页 > news >正文

动态内存管理的了解及使用

目录

1.什么是动态内存?

2.为什么要使用动态内存分配空间?

3.动态内存开辟函数malloc,calloc,realloc

3.1 malloc

3.2 calloc 

3.3 realloc

3.4 头文件包含

4.动态内存释放函数free

5.动态内存的基本常见错误

        5.1 对NULL的解引用操作

        5.2 对动态开辟空间的越界访问

5.3 误对非动态内存进行释放

5.4 使⽤free释放⼀块动态开辟内存的⼀部分

5.5 动态开辟内存忘记释放(内存泄漏)

6.c/c++中的内存区域划分

1.什么是动态内存?

        从内存区域上来看,一般的静态变量,比如

int a;
char c;
int arr[10]={1,2,3,4,5};
char arr2[10]="abcde";
int *p1;
char *p2;
double d;

        诸如此类都是在栈空间分配的,定义是不会变的,固定死的,使用完后由系统自动释放空间。而动态内存,是在堆区开辟的,由用户开辟,用户释放,系统不会主动进行释放操作。可以根据实际需求对内存分配进行扩大。

2.为什么要使用动态内存分配空间?

        在上述代码中,都是静态内存,有那么几个缺点:

  • 无论是变量,整形数组,字符串数组亦或是指针,内存大小都是固定的,一旦确定不可更改。
  • 数组在申明的时候必须要确定长度,数组空间确定了也不可以改变。

        那么在后续使用中,这种静态内存分配不可能满足所有需求,很多程序在运行后会越来越大,我们也无法准确预知程序所需要的内存空间到底是多少,所以需要更加灵活的方式来定义内存空间,程序就不会崩溃。所以可以由用户手动开辟,手动释放的方式——动态内存分配诞生了。

3.动态内存开辟函数malloc,calloc,realloc

        C语言库中提供了几种内存开辟函数,malloc,calloc和realloc,这些都有不同的功能。一一介绍

3.1 malloc

        malloc在cplusplus中是这样的,其实动态内存函数在之前的通讯录优化扩容部分是有提及并且使用它去开辟过数组空间的。它会申请一块连续的空间,并且返回一个指向内存块开头的指针,内存块中数据是未初始化的。

        如果开辟失败,则会返回一个NULL的指针,所以要对返回值进行检查。

        由于返回的类型是void*,所以malloc的时候要强制转化。

malloc

举个例子:

int main()
{
	//申请空间
	int*p =(int*)malloc(60);
	if (p==NULL)
	{
		perror("malloc");
		return 1;
	}

	int arr[] = {0};
	int i = 0;
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

指针p的变化:可以看出是随机的(调试界面)

3.2 calloc 

        calloc是C语言提供的另外一种动态内存开辟的方法,它和malloc不同在于它在开辟之初能直接初始化内存空间为0。开辟的是num个大小为size元素

calloc

例子:

int main()
{
	int* p = (int*)calloc(10,sizeof(int));
	if (p==NULL)
	{
		printf("%s\n",strerror(errno));
		return 1;
	}
	int i = 0;
	/*for ( i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}*/
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

 调试界面的变化:可以看出p是被初始化为0了。

不去对指针进行任何赋值操作,输出结果是: 

赋值后是:

3.3 realloc

         realloc也是一个动态开辟的方法,不过它相较于malloc和realloc更为灵活。来看看它的属性

realloc

        这个单词的前缀是re,而在英语单词中re的意思是重新,再次的意思。所以realloc是重新申请一块内存块。有时会我们发现申请的空间太小了,有时候我们⼜会觉得申请的空间过大了,那为了合理的使用,我们对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。

  • ptr是要调整改变的内存地址
  • size是对内存要重新分配多少空间。
  • 返回重新调整后的内存起始地址
  • 在调整内存的新大小之后,会根据情况不同有两种情况
  1. 如果原来空间够大,即是原地扩容,在原来空间中追加空间。
  2. 空间中所剩余的空间不够分配后续的空间,就会另外找一块足够大的空间,这个空间不一定和原空间地址连续。开辟后,会把原空间的数据拷贝到新空间,然后返回新内存块的起始位置。

举个例子:

int main()
{
	int* p = (int *)malloc(40);
	if (p==NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for ( i = 0; i < 5; i++)
	{
		*(p + i) = i;
	}
	//不够了,进行扩容
	int* ptr = (int *)realloc(p,1000);
	if (ptr!=NULL)
	{
		p = ptr;
	}
	
	for ( i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

        调试界面中,一开始指针的地址p是这样的。

        再走下去到这一步,我们把realloc的地址给了指针ptr存放,可以看出p指向的地址和ptr指向的地址不同,再把ptr赋值给p,p一定会变成ptr的地址

 F10走

 (至于为什么原地空间不够大,是因为我扩容了1000个字节,如果只扩容40,100),我想应该是原地扩容,地址不会变动,可以自行尝试。

这是不同情况下realloc的空间示意图,是在通讯录第二篇有画过的。

3.4 头文件包含

        开辟函数malloc,calloc,realloc和释放函数free都是要包含头文件的,<stdlib.h>声明就可以了。

4.动态内存释放函数free

        free函数是专门为了上面三个扩容函数准备的。为什么要free呢,因为静态内存是系统自己在栈区开辟的,你去使用只需要负责用,用完系统自动回收销毁空间。而动态内存是手动开辟的。就可以不用太正式的语言,举个很通俗易懂的比方就是。

        就好比你去餐厅吃饭,这就是静态开辟,你所做的是点菜,吃饭,结账。剩下都是餐厅要做的事情。餐厅要洗菜,切菜,做菜,最后你吃完要打扫餐桌,洗碗,处理厨余垃圾。系统就是餐厅,静态内存就是你吃的菜。

        而你觉得餐厅的饭可能是预制菜,不和你胃口,不营养,你想自己在家做一桌好吃的菜。那就是动态开辟,一切都要自己动手,从买菜,做菜,吃饭到收拾。前面是malloc/calloc/realloc开辟,收拾就是你要free,不free就好比你不收拾,要么菜都发霉了,引来虫子,细菌,然后你被家长教育一顿。而在编程里,不主动free置NULL,系统就会崩溃,内存泄漏,野指针等等问题。

        free的使用很简单,就是把内存开辟所在的指针变量释放,置NULL就可以了。free只是释放了空间,要置空才行,如果不置空,指针变量依然指向那块内存,内存的地址是还在的。在以后的代码中若不小心继续调用指针变量,会出现不可预料的错误。

我特意去调试了一下看看区别:

        这是free后还没置NULL的内存地址,断点在50行,走一步,free了,内存地址不变(因为我是重新调试,地址是系统随机分配的,所以地址不可能和上面的保持一致),还在!只是内存所存放的内容成了随机值。那去访问一个随机值的内存空间,一次两次没事是侥幸,但肯定是有问题的,还是要严谨。

再走一步就是变成了0x00000000,且不可读取内存,说明是NULL。 

5.动态内存的基本常见错误

        5.1 对NULL的解引用操作

 void test() 
{ 
int *p = (int *)malloc(INT_MAX/4);    
 *p = 20;//如果p的值是NULL,就会有问题 
 free(p);
 }

        在编译器里,*p那一行会产生警告,说是取消对null的解引用操作,其实就是在前面没有对指针p进行检查是否为空,因为系统可能无法一次性分体量为(INT_MAX/4)的内存空间给指针p。很可能存在开辟失败,就成了空指针。而*p=20,是空指针的话显然是有问题的。

        5.2 对动态开辟空间的越界访问

void test() {
 int i = 0;  
 int *p = (int *)malloc(10*sizeof(int));
 if(NULL == p)    
{       
  exit(EXIT_FAILURE);   
}    
 for(i=0; i<=10; i++)
 {      
*(p+i) = i;//当i是10的时候越界访问   
}  
   free(p); 
}

        内存只开辟了40个字节,而i到10的时候是0-10,有11个数据,所以是越界访问。 

5.3 误对非动态内存进行释放

void()
{
int a =10;
int *p =&a;
free(p);
}

        这对吗?肯定是错的,你都没有malloc内存函数开辟空间,就要free,想free什么? 

5.4 使⽤free释放⼀块动态开辟内存的⼀部分

 void test()
 { 
 int *p = (int *)malloc(100);    
 p++;    
 free(p);//p不再指向动态内存的起始位置 
 }

        这是我有时候会犯的错误,经常会报警告,后来DeepSeek一查才直到p++后会使指针不指向起始位置,产生局部释放 ,AI的辅助有时候使让人一下顿悟。

5.5 动态开辟内存忘记释放(内存泄漏)

void test() 
{  
 int *p = (int *)malloc(100); 
 if(NULL != p)     
    {        
 *p = 20;  
    } 
}
int main()
{
test();
return 0;
}

       没有在函数中进行动态开辟内存释放,引发内存泄露问题。函数调用结束后就销毁了,内部没有进行释放,也就释放不了了。

        忘记释放不会复用动态开辟的空间会造成内存泄漏。

        切记:动态开辟的空间⼀定要释放,并且正确释放。         

6.c/c++中的内存区域划分

        关于动态内存的开辟知识 ,在c语言只是涉及,而在后续学习数据结构中,会不断学习 。数据结构中malloc,calloc,realloc和free会不断,频繁见面的。所以还是需要熟练的掌握和使用。

相关文章:

  • Flink-DataStreamAPI-执行模式
  • C# Enumerable类 之 数据排序
  • 【项目实战】Spring AI集成DeepSeek实战指南(硅基流动平台版)
  • JSAR 基础 1.2.1 基础概念_空间小程序
  • cdn取消接口缓存
  • 2025-03-08 学习记录--C/C++-C 语言 判断一个数是否是完全平方数
  • [网络爬虫] 动态网页抓取 — 概念引入
  • 基于opencv的hsv色块检测
  • vue和easyui渲染问题
  • 代码随想录二刷|图论2
  • Java高频面试之集合-06
  • 深度链接技术解析:openinstall如何通过场景还原优化用户体验?
  • 如何利用Postman对比出新旧接口之间的差异(Diff)
  • dfs:五子棋对弈15蓝桥杯a组1题
  • 数据结构第八节:红黑树(初阶)
  • 【图灵商城项目-登录失败:密码不正确,问题已解决】
  • Python使用MyQR生成动态二维码
  • 【LangChain】理论及应用实战(2)
  • Git基础之基本操作
  • 【GPT入门】第2课 跑通第一openAI程序
  • 江西贵溪:铜板上雕出的国潮美学
  • 陕西一村民被冒名贷款40余万续:名下已无贷款,将继续追责
  • 足球少年郎7月试锋芒,明日之星冠军杯构建顶级青少年赛事
  • 印称印巴军事行动总指挥同意将局势降级
  • 香港暂停进口美国北达科他州一地区禽肉及禽类产品
  • 巫蛊:文化的历史暗流