动态内存管理【malloc,calloc,realloc和free的理解】【柔性数组的概念】

一.为什么要有动态内存分配

我们知道,当我们创建变量的时候,我们会向系统申请一定大小的空间内存。比如int a=10或者int arr[10];我就向内存申请了4或者40个字节的大小来存放数据。但是当我们一旦申请好这个空间,大小就无法调整了。但是对于空间的需求,不仅仅就只有上面的情况。有时候我们需要的空间大小只有在程序运行的时候才能知道,那么数组编译时开辟空间的方式就不能满足了。

在C语言中,引入了动态内存开辟,让程序员可以自己申请和释放空间,就比较灵活了。

注意,以下介绍的函数头文件都是stdlib.h

二.malloc,free,calloc和realloc

1.malloc函数的理解

这个函数可以向内存申请一块连续可用的空间,并且返回指向这块空间的指针。

1.如果开辟空间成功,则返回一个指向开辟好的空间的指针。

2.如果开辟失败,则返回一个NULL指针,因此我们在使用malloc的时候一定要检查。

3.因为它的返回值是void*,malloc函数不知道返回值的类型,具体在使用时还是程序员来定。

4.如果参数size(单位是字节)为0,malloc的行为是标准没有定义的,取决于编译器。

比如我想开辟能存放5个整数的空间

int main()
{
	int* p = (int*)malloc(20);//这里开辟20个字节的空间,把起始地址放在p里
	if (p == NULL)//判断也没有开辟成功
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(p +i) = i + 1;
	}
	return 0;
}

我们可以调试看一下在内存中它是怎么开辟的。

代码运行完毕后:

这些就是malloc函数的使用了。还有我上面写的第4条,明明我用malloc是来申请空间的,结果我要申请0个字节的空间,这就显得有点没用了。

我们需要注意的一点,这些开辟的空间都在内存的哪一个区域?不管是malloc,free,calloc或者realloc这些跟动态内存有关的都存放在内存的堆区。而我们创建的像是局部变量,形式参数这些是在栈区。而静态变量,全局变量,这些在静态区。

2.free函数的理解

这个函数是专门用来做动态内存的释放和回收的。

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

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

也就是说,我想释放哪里的空间,就把这一块空间的起始地址给这个函数的参数。

free的作用就是把空间的使用权限还给了操作系统,还是上面的代码:

#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(20);//这里开辟20个字节的空间,把起始地址放在p里
	if (p == NULL)//判断也没有开辟成功
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(p +i) = i + 1;
	}
	free(p);
	p = NULL;
	return 0;
}

调试运行之后是这样的:

注意,我们在把p指向的那一块空间给释放了之后,p指针还是存在的,但是它没有指向的东西了,此时的p就是一个野指针,那我们就必须给这个野指针一条绳子NULL来栓住它。

3.calloc函数的理解

calloc函数也是用来动态申请空间的,但是它的用法跟malloc不太一样。

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

2.与malloc的区别就是,malloc在开辟空间的时候没有初始化这个功能。

举个例子:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p = NULL)
	{
		perror("calloc");
		return 1;
	}
	return 0;
	free(p);
	p = NULL;
}

这里我没有进行赋值操作,但是在内存中这些值就已经变成了0。

其他的地方跟malloc是一样的,在释放空间后也是需要给上一个NULL。

4.realloc函数的理解

这个函数是基于动态内存的,如果我们在开辟空间的时候,觉得我们申请的空间太大了或者太小了,为了能够合理的使用内存,我们可以使用realloc函数实现对动态开辟内存大小的调整。

1.ptr是要调整的内存地址,size是调整之后的大小。

2.返回值是调整之后的内存起始位置。

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

4.realloc函数在调整时有两种情况,一种是原来的空间后面有足够的空间大小,另一种是后面的空间不够了。

我先简单写一下代码,简单说一下第四点会出现的问题。

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)calloc(5, sizeof(int));//这里原本是20个字节的大小
	if (p = NULL)
	{
		perror("calloc");
		return 1;
	}
	int* ptr=(int*)realloc(p, 40);//这里调整为40个字节
	if (ptr != NULL)//这里我没有直接把开辟空间的起始地址给p,就是害怕万一开辟空间失败了,原来的空间也没了
	{
		p = ptr;//这里空间调整成功了就可以使用我们开辟的40个字节的空间了
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			*(p + i) = i + 1;
		}
		free(p);//一定要记得释放,否则会存在内存泄漏的问题
		p = NULL;//释放空间了一定要置为空指针
	}
	else
	{
		perror("realloc");
	}
	return 0;
}

 这里就是开辟空间扩大的情况。

情况一:原内存之后有足够的空间的话,会直接在原来的空间的后面再开辟20个字节,凑够40个字节。

情况二:原内存之后没有足够的空间的话,这个函数会在堆区的内存里找一个新的空间,并且是满足我们所要开辟的空间的,而且会把原来空间的数据拷贝一份到新的空间,原来的空间释放掉,并且会把新的空间的起始地址返回。

情况三:如果调整失败了,直接返回NULL。

注意:realloc不仅仅可以调整空间的,它也可以开辟空间,我们想一下,如果我给ptr的参数是一个空指针呢?

#include<stdlib.h>
int main()
{
	realloc(NULL, 20);//这个等价于malloc(20)
	return 0;
}

以上就是动态空间分配需要使用到的函数。

5.总结4个函数

当我们在申请空间后,不再用这块空间了,虽然,当程序结束的时候,操作系统会回收这一块空间,但最好是我们主动去用free释放,尽量做到谁(函数)申请的空间谁释放。因为如果我的这块空间一直不退出的话这一块内存就一直被占用着,谁也用不了。如果不能释放,要告诉使用的人,要记得释放。

三.柔性数组

关于柔性数组,有几点需要介绍一下:

1.它存在于结构体中,是结构体的最后一个成员

2.最后的一个成员是数组,而且没有指定大小

struct S
{
	char c;
	int i;
	int arr[];//这个就是柔性数组,并没有规定具体的大小或者写成int arr[0]
};

这就是柔性数组

1.柔性数组的特点

1.结构体里的柔性数组成员前面必须至少有一个其他成员

2.sizeof计算这样含柔性数组大小的时候,不包括柔性数组的内存

3.包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

关于前两点

直接就只是i的大小。

再来看第三点:

struct S
{
	int i;
	int arr[];//这个就是柔性数组,并没有规定具体的大小
};
#include <stdio.h>
int main()
{
	struct S* ps=(struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));
    free(ps);
    p=NULL;
	return 0;
}

除了我要开辟的int大小的空间,后面我又加了一个5 * sizeof(int)这个是我要额外开辟的空间。

我们既然是用malloc开辟的空间,那么我们是不是就可以使用realloc来改变这一块空间,随便怎么变大变小。对应到这个数组上,我们不就是把这个数组变成“柔性”了吗?

2.柔性数组的使用

了解了它的一些基本特点,还有前面的几个函数,使用起来也就很简单了。

struct S
{
	int i;
	int arr[];//这个就是柔性数组,并没有规定具体的大小
};
#include <stdio.h>
int main()
{
	struct S* ps=(struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));
	if (ps == NULL)//没有开辟成功
	{
		return 1;
	}
	ps->i = 100;
	int a = 0;
	for (a = 0; a < 5; a++)
	{
		ps->arr[a] = a + 1;
	}
	struct S* ret = (struct S*)realloc(sizeof(struct S) + 10 * sizeof(int));//调整为44个字节大小
	if (ret != NULL)
	{
		ps = ret;
	}
	free(ps);
	ps = NULL;
	return 0;
}

到这里,关于动态内存管理的相关知识就已经讲完了。感谢大家的观看,如有错误,请大家多多指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值