首页 > 系统相关 >C语言-动态内存管理

C语言-动态内存管理

时间:2024-03-16 15:32:03浏览次数:23  
标签:动态内存 管理 int void free C语言 str NULL

动态内存管理

为什么存在动态内存分配

动态内存函数介绍

malloc和free

calloc

realloc

常见动态内存错误

1对NULL指针的解引用操作

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

3 对非动态开辟内存使用free释放

4 使用free释放一块动态开辟内存的一部分

5.对同一块动态内存多次释放

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

相关的经典题目


 

为什么存在动态内存分配

我们已经掌握的内存开辟方式有 :

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开游10个学节的连续空间

但是上述的开辟空间的方式有两个特点:

1.空间开辟大小是固定的。

2.数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配但是对于空间的需求,不仅仅是上述的情况。

有时候我们需要的空间大小在程序运行的时候才能知道那数组的编译时开辟空间的方式就不能满足了。

这时候就只能试试动态内存开辟了。

动态内存函数介绍

malloc和free

void* malloc(size_t size);
  • 这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:

void free (void* ptr);

free函数用来释放动态开辟的内存。

如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

如果参数 ptr 是NULL指针,则函数什么事都不做。

malloc和free都声明在 std1ib.h头文件中。

举个例子:

#include <stdio.h>
#include <errno.h> 
#include <stdlib.h>
int main()
{
	int arr[10] = { 0 };//申请的空间在栈区

	//动态内存开辟
	int* p = (int*)malloc(40);//申请的空间在堆区
	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;//释放内存空间
	
	//没有free
	//也并不是说内存空间就不回收了
	//当程序退出的时候,系统会自动回收内存空间
	return 0;
}

calloc

C语言还提供了一个函数叫ca11oc

ca11oc函数也用来动态内存分配。原型如下:

void* ca11oc(size_t num, size_t size);

 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。

与函数 ma11oc的区别只在于 ca11oc 会在返回地址之前把申请的空间的每个字节初始化为全0。

举个例子 :

int main()
{
	//开辟10个整形空间
	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++)
	{
		printf("%d  ", *(p + i));
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

realloc

realloc函数的出现让动态内存管理更加灵活。

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整。

那rea11oc函数就可以做到对动态开辟内存大小的调整。

函数原型如下 :

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

ptr 是要调整的内存首地址

size 调整之后新大小

返回值为调整之后的内存起始位置。

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

realloc在调整内存空间的是存在两种情况 :

情况1:原有空间之后有足够大的空间直接在原空间后面增加至新的大小,返回原空间首地址

情况2:原有空间之后没有足够大的空间开辟新的一块足够大空间将原来空间的数据拷贝过来释放原来空间返回新开辟空间的首地址

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用开辟的内存
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//扩容
	int* ptr = (int*)realloc(p, 80);//80是新的大小
	if (ptr != NULL)
	{
		p = ptr;
	}
	//使用
	for (i = 0; i < 10; i++)
	{
		printf("%d  ", *(p + i));
	}
	//释放
	free(p);
	p = NULL;

	return 0;
}

常见动态内存错误

1对NULL指针的解引用操作

void test()
{
	int* p = (int*)malloc(INT_MAX / 4);

	*p = 20;			//如果p的值是NULL,就会有问题
						//malloc返回的指针要先判断不是空指针再使用  
	free(p);
	p = NULL;
}

注意:对malloc和calloc和realloc返回的指针要判断不为空再使用

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

void test()
{
	int* p = (int*)mal1oc(10 * sizeof(int));
	if (NULL == p)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	int i = 0;
	for (i = 0; i <= 10; i++)		//对动态开辟空间的越界访问是错误的
	{
		p[i] = i;			
	}
	free(p);
	p = NULL;
	return 0;
}

注意:即便是动态内存空间,也不能对齐越界访问。

3 对非动态开辟内存使用free释放

void test()
{
	int a = 10;
	int* p = &a;
	//...

	free(p);			// 对非动态开辟内存使用free释放是不行的
	p = NULL;
}

注意:free函数只能释放动态内存开辟的空间,对非动态开辟内存使用free释放是不行的。

4 使用free释放一块动态开辟内存的一部分

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p = i;
		p++;
	}
	//经过for循环里的p++后,p已经没有再指向这块空间的起始位置了
	free(p);//所以这里是错误的,不能使用free释放动态空间的一部分
	p = NULL;
	return 0;
}

注意:不能使用free释放动态内存的一部分

5.对同一块动态内存多次释放

int main()
{
	int* p = (int*)malloc(40);

	//...
	free(p);

	//...
	free(p); //不能对同一块动态内存多次释放

	return 0;
}

注意:不可以对同一块内存空间进行多次释放,每次释放内存空间后,将对应指针置为空指针,可以避免程序因为这种情况报错

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

void test()
{
	int* p = (int*)malloc(100);
	//...

	int flag = 0; 
	scanf("%d", &flag); 
	if (flag == 5)
		return;//flag == 5,函数返回程序结束p没被释放
	free(p); 
	p = NULL;
}
int main()
{
	test();
	return 0;
}

或者

int* test()
{
	//开辟的
	int* p = (int*)malloc(100); 
	if (p == NULL)
	{
		return p;
	}
	//...

	return p;
}
int main()
{
	int* ret = test();
	//忘记释放了

	return 0;
}

注意:开辟的内存空间及时释放避免造成内存泄漏

相关的经典题目

1.运行Test函数会产生什么结果

void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL; 
	GetMemory(str); 
	strcpy(str, "hello world");
	printf(str);
}

分析:


void GetMemory(char* p)//把空指针传过来,p一开始是NULL
{
	p = (char*)malloc(100);//函数体内部p被赋值为动态内存的首地址
						   //函数结束回到Test,p被销毁
						   //动态内存未释放造成内存泄漏
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);//传值调用,GetMemory函数没有改变str指针,str指针仍为NULL
	strcpy(str, "hello world");//没办法把hello world复制到一个空指针
	printf(str);//不会打印
}
int main()
{
	Test();
	return 0;
}

这段代码想正确实现想要的功能应该进行传址调用,修改如下

void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);//str存放的就是动态内存开辟的100字节的首地址
	strcpy(str, "hello world");
	printf(str);
	//释放
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}

2 运行Test函数会产生什么结果

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL; 
	str = GetMemory(); 
	printf(str);
}

分析:

char* GetMemory(void)
{
	//返回栈空间地址的问题
	char p[] = "hello world";//局部变量
	return p;//返回字符数组p的首地址, 但字符数组这块空间在函数调用结束后将还给操作系统
			 //这块空间的内容有可能被覆盖
}
void Test(void)
{
	char* str = NULL;//创建str指针
	str = GetMemory();//调用GetMemory函数,返回的指针为野指针
	printf(str);
}
int main()
{
	Test();
	return 0;

3运行Test函数会产生什么结果

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

分析:

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);//动态空间已释放,但忘记把str置为空指针
	if (str != NULL)
	{
		strcpy(str, "world");//到这str是野指针,属于非法访问
		printf(str);
	}
}

标签:动态内存,管理,int,void,free,C语言,str,NULL
From: https://blog.csdn.net/weixin_46375885/article/details/136763024

相关文章

  • rt-thread 线程管理
        一个实时操作系统,最重要的就是线程管理,rt-thread是一个硬实时系统,支持抢占式和时间片轮转的方式进行线程调度。    接下来简单描述下线程管理,并重点描述如何实现线程调度。    首先,一个程序是为了实现某种具体功能,而这个功能可能是由诸多小功能......
  • c语言程序设计——实验报告一
    c语言程序设计——实验报告一实验项目名称:实验一熟悉C语言运行环境实验项目类型:验证性实验日期:2023年3月14日一、实验目的下载安装Devc6.0程序。了解在该系统上如何进行编辑、编译、连接和运行一个C程序。通过运行简单的C程序了解C程序的特点。二、实验硬、软件环境W......
  • 软件项目管理资料汇总(规格说明书、详细设计、测试计划、验收报告)
      前言:在软件开发过程中,文档资料是非常关键的一部分,它们帮助团队成员理解项目需求、设计、实施、测试、验收等各个环节,确保项目的顺利进行。以下是针对您提到的各个阶段的文档资料概述:所有资料获取:点击获取开发阶段需求规格说明书:详细描述了软件系统的功能需求、非功......
  • 网络安全等级保护2.0自查表 | 管理部分
    等保2.0自查表,管理部分,参考标准:GB∕T22239-2019《信息安全技术网络安全等级保护基本要求》GB∕T28448-2019《信息安全技术网络安全等级保护测评要求》等保2.0自查表管理部分范围控制点检查项三级要求二级要求安全管理制度安全策略应制定网络安全工作......
  • python调用c语言API
    python调用C语言API,一般使用内置的ctypes库.但是这个库用起来不那么方便,主要是需要进行函数和数据结构的包装.如下所示:#结构定义classCOORD(Structure):"""COORDstructure"""_fields_=[("X",SHORT),("Y",SHORT)]#函数定义UpdateP......
  • C语言 01 概述
    历史C语言是一门面向过程的计算机编程语言,于1972年诞生于贝尔实验室。C语言是丹尼斯·里奇(DennisRitchie)以B语言为基础发展而来,因此他被称为C语言之父。C语言为什么命名为C?1967年,剑桥大学的MartinRichards对CPL(CombinedProgrammingLanguage)语言进行了简化......
  • 如何提高工作/学习效率—管理好网站资源与资料文件
          在日常的学习和工作中,我们经常要利用互联网(百度、博客等),那我们怎么样才能高效快捷的利用好网络资源呢?小编今天在这里为大家分享一下自己的方法,管理好网站资源与资料文件,如有不同见解,恳请指教~        最常用的搜索网站就是百度了,当我们需要使用博客......
  • ssm/php/node/python高校实习实训管理系统
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:在当今社会,随着教育模式的不断创新与发展,高校教育已经不仅仅局限于传统的课堂理论教学。实践能力的培养被越来越多的高等教育机构所重视,实习实训成为了......
  • ssm/php/node/python学籍管理系统
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义标题:探索学籍管理系统2mc5o的应用与价值在信息技术高速发展的今天,教育管理的现代化已经成为提升教学质量和效率的关键。学籍管理系统2mc5o作为一款创新的管理工......
  • ssm/php/node/python学籍管理系统
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:随着信息技术的飞速发展,教育行业也在逐渐实现数字化转型。学籍管理作为学校管理工作中的一个重要环节,其效率和准确性直接影响到学校的教学质量及管理水......