C语言之动态内存管理(malloc calloc realloc)

C语言之动态内存管理

文章目录

  • C语言之动态内存管理
    • [1. 为什么要有动态内存管理](#1. 为什么要有动态内存管理)
    • [2. malloc 和 free](#2. malloc 和 free)
      • [2.1 malloc](#2.1 malloc)
      • [2.2 free](#2.2 free)
      • [2.3 例子](#2.3 例子)
    • [3. calloc 和 realloc](#3. calloc 和 realloc)
      • [3.1 calloc](#3.1 calloc)
      • [3.2 realloc](#3.2 realloc)
    • [4. 常见的动态内存错误](#4. 常见的动态内存错误)
      • [4.1 对NULL指针的解引⽤操作](#4.1 对NULL指针的解引⽤操作)
      • [4.2 对动态开辟空间的越界访问](#4.2 对动态开辟空间的越界访问)
      • [4.3 对⾮动态开辟内存使⽤free释放](#4.3 对⾮动态开辟内存使⽤free释放)
      • [4.4 使⽤free释放⼀块动态开辟内存的⼀部分](#4.4 使⽤free释放⼀块动态开辟内存的⼀部分)
      • [4.5 对同⼀块动态内存多次释放](#4.5 对同⼀块动态内存多次释放)
      • [4.6 动态开辟内存忘记释放(内存泄漏)](#4.6 动态开辟内存忘记释放(内存泄漏))
    • [5. 总结](#5. 总结)

1. 为什么要有动态内存管理

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

c 复制代码
#include <stdio.h>

int main()
{
	int val = 20;
	int arr[10] = { 0 };
	return 0;
}

上述的开辟空间的⽅式有两个特点
• 空间开辟大小是固定的

•数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整

所以C语⾔引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间

2. malloc 和 free

mallocfree函数都是在stdlib.h头文件中声明的

2.1 malloc

C语言中提供了一个动态内存开辟的函数:

c 复制代码
void* malloc (size_t size);

其中size为要开辟的内存空间的大小,单位为字节

这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针

*•如果开辟成功,则返回一个指向开辟好的内存空间的指针
•如果开辟失败,则返回一个NULL指针
•返回类型为void ,因为malloc函数不知道要开辟什么类型的内存空间,只知道要开辟的大小
•如果参数为0,malloc函数的行为标准是未定义的,取决于编译器

2.2 free

C语言还提供了一个的函数,专门用来做动态内存的释放和回收的:

c 复制代码
void free (void* ptr);

ptr为要释放内存空间的指针

•如果参数ptr指向的内存空间不是动态开辟的,那么free函数的行为是未定义的
•如果参数ptrNULL,则函数什么都不做

如果不对malloc calloc realloc 开辟的空间进行释放,即使出了作用域也不会销毁,有可能导致内存泄漏
释放的方式
1. free
2. 直到程序结束,由操作系统释放

2.3 例子

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int* p = (int*)malloc(10 * sizeof(int)); //开辟40个字节的空间
	//判断是否为NULL指针
	if (p == NULL)
	{
		perror("malloc fail\n"); //perror为错误信息打印
		return 1;

	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
		//*p = i;   //如果使用这种方法,p指针向后走了,在下面打印时,就找不到首元素的地址了
		//p++;
	}
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);  //释放
	p = NULL; //将指针置NULL,如果不置NULL,下面解引用p时,p就是野指针

	return 0;
}

代码运行结果:>

0 1 2 3 4 5 6 7 8 9
malloc开辟空间时,是不会给空间初始化的,如果直接打印,会打印出随机值

3. calloc 和 realloc

3.1 calloc

C语⾔还提供了⼀个函数叫 calloccalloc 函数也⽤来动态内存分配

c 复制代码
void* calloc (size_t num, size_t size);

num为要开辟的元素个数
size为开辟元素的元素大小,单位为字节

calloc为开辟num个大小为size元素的内存空间,并且将内存中每个字节初始化为0
calloc的使用方法和malloc一样,主要区别在于calloc会初始化元素

例子:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int* p = (int*)calloc(10 ,sizeof(int));
	//判断是否为NULL指针
	if (p == NULL)
	{
		perror("calloc fail\n");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

代码运行结果:>

0 0 0 0 0 0 0 0 0 0

3.2 realloc

C语言中有一个函数用来调整动态内存开辟后的大小

c 复制代码
void* realloc (void* ptr, size_t size);

ptr为要调整的内存地址
size为调整后的内存大小

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

• 当我们发现我们使用malloc calloc realloc申请的内存空间不够时,我们可以使用realloc进行扩容

•返回值为调整之后的内存的起始位置(不一定是原内存地址)
•如果开辟失败则返回一个NULL
•如果开辟成功则分以下两个情况:

情况1:原有空间之后有⾜够⼤的空间
情况2:原有空间之后没有⾜够⼤的空间

情况1:在原有内存后边直接追加空间,原来的空间的数据不变

情况2:原有内存之后的空间不足以最加空间,那么realloc会在堆区中找到一块足够开辟新大小的空间,将旧空间中的数据拷贝到新空间,并且将旧空间释放,同时返回新空间起始位置的地址

realloc的用法除了为开辟的内存进行扩容,也可以和malloc一样

例子:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	//判断是否为NULL指针
	if (p == NULL)
	{
		perror("malloc fail\n");
		return 1;
	}
	int* tmp = (int*)realloc(p, 100*sizeof(int));
	if (tmp != NULL)
	{
		p = tmp;
	}
	else
	{
		perror("relloc fail\n");
		return 1;
	}
	//使用
	//..........
	free(p);
	p = NULL;
	return 0;
}

这次的运行结果就是情况2(开辟100个字节的大小时,可能会出现),当后面的空间不够时, realloc就会找一块新的空间

这次只开辟了40个字节的空间,属于情况1,后面的空间足够时, realloc会直接在后面追加空间

4. 常见的动态内存错误

4.1 对NULL指针的解引⽤操作

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int* p = (int*)malloc(40);
	*p = 20; //如果malloc开辟空间失败,p可能是NULL,此时p为野指针
	return 0;
}

在VS2022中,编译器会进行提示,我们得对可能出现NULL的情况进行处理

在使用malloc calloc realloc开辟空间时,最好对返回值进行判断,当不为NULL再使用

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

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc fail\n");
		return 1;
	}
	int i = 0;
	for (i = 0; i <= 10; i++)  //只有10个元素的空间,却访问了第11个元素,访问越界了
	{
		*(p + i) = i;
	}
	free(p);
	p = NULL;
	return 0;
}

4.3 对⾮动态开辟内存使⽤free释放

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
	int a = 10;
	int* p = &a;
	free(p);
	return 0;
}

当用free释放了不是由malloc calloc realloc开辟的空间时,就会报错

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

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int a = 10;
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc fail\n");
		return 1;
	}
	//使用
	//......
	p++;
	free(p);
	p = NULL;
	return 0;
}

当用free释放了开辟空间的一部分时,就会报错

4.5 对同⼀块动态内存多次释放

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int a = 10;
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc fail\n");
		return 1;
	}
	//使用
	//......
	free(p);
	free(p);
	p = NULL;
	return 0;
}

对一块动态开辟的内存进行多次free释放

在上述代码中如果free释放NULL,则没有问题,因为free的参数为NULL时,则什么都不做

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int a = 10;
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc fail\n");
		return 1;
	}
	//使用
	//......
	free(p);
	p = NULL;
	free(p);
	p = NULL;
	return 0;
}

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

c 复制代码
#include <stdio.h>
#include <stdlib.h>

void test()
{
	int* p = (int*)malloc(100);
	if (p != NULL)
	{
		*p = 20;
	}
}
int main()
{
	test();
	while (1); //死循环,让程序不结束
	return 0;
}

当动态开辟的内存不释放时,就会一存在,在上述代码中,调用了test函数,开辟了100个字节的空间,同时赋值,出函数时,p被销毁了,但是开辟的空间并没有被销毁,没人可以使用,也没人可以释放,就会导致内存泄漏

5. 总结

一丶
在使用malloc calloc realloc开辟的空间时,要对其进行判断,当不为NULL的再进行使用
二丶
当不使用动态开辟的内存时,将其free释放,同时将指针置NULL,防止可能出现的内存泄露和野指针
三丶
不对不是动态开辟的空间free,不连续对动态开辟的空间free,同时free动态开辟的空间时,要给开辟的起始地址,不能free部分空间

相关推荐
Ajiang28247353041 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
幽兰的天空1 小时前
Python 中的模式匹配:深入了解 match 语句
开发语言·python
Theodore_10224 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
网易独家音乐人Mike Zhou4 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
----云烟----6 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024066 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic6 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it6 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康6 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神7 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式