C语言——动态内存管理

目录

内存相关知识

栈区 堆区 静态区

malloc

calloc

函数介绍

calloc 与 malloc 的区别

realloc

语法介绍

开辟空间的两种情况

realloc 与 malloc

free

结语


内存相关知识

我们平常开辟空间时,可能会有以下几种方式:

int a = 0;
char b = 'A';
int arr[10] = { 0 };

比如创建一个整形变量,字符变量,或是直接创建一个数组,连续开辟一大块空间

但是,这些空间都是在栈区上开辟的,如果我们在函数中这么写的话,就会导致因为空间销毁而无法找到该空间,如下:

char* test()
{
	char arr[20] = "hello world!";
	return arr;
}

int main()
{
	char *p = test();
	printf("%s\n", p);
	return 0;
}

我们会发现,打印的结果并不是预期中的 hello world ,这是因为,test函数中创建的数组在出了函数之后就被销毁了,而我们返回的地址指向的是一块还给了操作系统的空间

栈区 堆区 静态区

我们需要知道,在我们日常创建的变量中,大部分都是在栈区上创建的,这部分区域上创建的变量都会因为出了函数而被销毁

另外两种不会被销毁的是放在静态区堆区上的变量

而我们今天要讲的动态内存管理,其开辟的空间就是在堆上的

malloc

如果要使用动态内存管理相关函数,需引头文件

#include <stdlib.h>

void* malloc (size_t size);

malloc 的参数是开辟需要的字节数

该函数的作用就是在堆区上开辟一块指定大小的空间,但是我们会看到 malloc 是void*

这是因为系统并不知道使用者要开辟的是什么类型的空间,但是做为使用者的我们是知道的,因此我们在使用的时候,我们需要将其强制类型转化一下

接着还要分两种情况:

1. 空间开辟成功,返回指向该空间的指针

2. 空间开辟失败,返回NULL

所以我们在开辟完空间之后,我们需要检查一下,返回的是否为空指针

如果为空指针,那么我们就可以用 perror 进行报错,并提前结束程序。如果不为空指针,那么就正常使用

比如我们要开辟一块大小为40个字节的空间,我们可以这么写:

#include<stdlib.h>
#include<stdio.h>

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//如果不为空,正常使用
	return 0;
}

当然,如果说你想开辟 10 个整形空间,你也可以使用 sizeof 让计算机帮你运算所需字节

int* p = (int*)malloc(10*sizeof(int));

calloc

函数介绍

C语言还提供了另一种开辟内存空间的函数——calloc

void* calloc (size_t num, size_t size);

参数 1 是需开辟的元素个数                   参数 2 是每个元素的大小(如:int 的大小是4个字节)

如果我们需要用 calloc 开辟 10 个整形空间的话,就可以这么写:

int* p = (int*)calloc(10 ,sizeof(int));
if (!p)
{
	perror("calloc");
	return 1;
}

calloc 与 malloc 的区别

我们来看这样一段代码:

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (!p)
	{
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *p++);
	}
	return 0;
}

当我们打印 malloc 开辟的空间的内容时,我们会发现上面的数字是很乱的,如若不初始化则根本无法使用

我们再来看一下 calloc

int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (!p)
	{
		perror("calloc");
		return 1;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *p++);
	}
	return 0;
}

我们会发现,相同的代码calloc malloc 的效果完全不一样

这时因为相比于 malloccalloc 会在空间开辟完之后将每一个元素都初始化为 0

所以,如果我们开辟的空间有初始化的要求,那么我们可以使用 calloc 来轻松实现 

realloc

语法介绍

realloc 的出现,使得动态内存管理变得更加灵活

当我们用 malloc calloc 开辟的空间用完了时,我们就可以使用 realloc 函数对空间进行扩容

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

参数 1 是指向要扩容空间的指针

参数 2 是扩容之后新的空间的总大小

如果申请失败,那么 realloc 函数会返回一个空指针(NULL)

开辟空间的两种情况

情况一:

原空间后面没有足够的空间,realloc 函数会再找一块总大小为扩容后总大小的空间,开辟完后释放元空间,并返回指向开辟后的空间的指针

假如我们原空间的大小是 40 个字节,现在我想将其扩大到 80 个字节

但是此时原空间后面的空间位置不够

这时我们就会再找一块有 80 个字节大小的空间,并返回指向该空间的指针

情况二:

原空间后面有足够的空间,那么 realloc 函数就会直接扩容

如上,原函数后面的空间足够

有了如上两种情况,那么我们在使用 realloc 函数的时候,我们就需要注意一点了

int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (!p)
	{
		perror("calloc");
		return 1;
	}	
	p = (int*)realloc(p, 20 * sizeof(int));

	return 0;
}

如上代码,这种方法是不可取的!!!

如果空间申请失败了,那么就会返回一个空指针,但是我们的指针 p 原本指向的是 calloc 申请出来的空间,但是现在我的 p 变成了空指针,那么我这块空间就找不到了

我们无法手动将其释放,那么就造成了内存泄漏

所以我们可以先用一个其他指针来接收,if 判断完申请成功之后,再修改回来

int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (!p)
	{
		perror("calloc");
		return 1;
	}	
	int* pr = (int*)realloc(p, 20 * sizeof(int));
	if (!pr)
	{
		perror("realloc");
		return 1;
	}
	else
	{
		p = pr;
	}
	return 0;
}

realloc 与 malloc

上文说:realloc 函数的第一个参数是指向需要扩容空间起始地址的指针

那如果,我们传一个空指针呢?

我们看到最后一条,上面写道:如果传了一个空指针,那么他的行为就会像一个 malloc 函数

由此我们知道,如果直接传一个 NULL,那么 realloc 函数会像 malloc 函数一样直接开辟一块空间,无初始化

int main()
{
	int* p = (int*)realloc(NULL, 10 * sizeof(int));
	if (!p)
	{
		perror("realloc");
		return 1;
	}
	int* pr = p;
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *pr++);
	}
	return 0;
}

我们会看到,这种行为是没有什么问题的,编辑器并没有报错

free

我们动态内存开辟的空间是在堆区上的,如果这块空间不需要了,那么我们就需要将其释放掉

C语言给我们提供了一个函数,就是用来释放动态开辟的空间的——free

该函数的唯一参数就是需要释放的空间的头指针

注意:如果传的是一个空指针,那么 free 函数将什么都不会做

接着我们再来看一段代码:

int main()
{
	int* p = (int*)malloc(40);
	if (!p)
	{
		perror("malloc");
		return 1;
	}
	free(p);
	return 0;
}

free 前指针 p 指向的地址

free 后指针 p 指向的地址

如上我们会看到,无论是 free 前 free 后,指针 p 指向的地址都没有变,这不就形成野指针了吗?

当我们以后操作忘记了我们指针 p 指向的空间被释放过时,我们再去使用指针 p 就会形成越界访问

综上,我们在使用 free 函数时,我们需要将 free 后的指针置为空,这样就能有效规避野指针的问题了,如下:

int main()
{
	int* p = (int*)malloc(40);
	if (!p)
	{
		perror("malloc");
		return 1;
	}
	free(p);
	p = NULL;
	return 0;
}

结语

综上,就是本期关于动态内存管理的相关内容了,如果对你有帮助的话,希望可以多多支持!

  • 16
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值