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部分空间

相关推荐
羊小猪~~几秒前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
binishuaio6 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE8 分钟前
【Java SE】StringBuffer
java·开发语言
就是有点傻12 分钟前
WPF中的依赖属性
开发语言·wpf
洋24020 分钟前
C语言常用标准库函数
c语言·开发语言
进击的六角龙22 分钟前
Python中处理Excel的基本概念(如工作簿、工作表等)
开发语言·python·excel
wrx繁星点点23 分钟前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
NoneCoder40 分钟前
Java企业级开发系列(1)
java·开发语言·spring·团队开发·开发
苏三有春40 分钟前
PyQt5实战——UTF-8编码器功能的实现(六)
开发语言·qt
Aniay_ivy1 小时前
深入探索 Java 8 Stream 流:高效操作与应用场景
java·开发语言·python