【C语言】动态内存管理详细讲解

目录

1.为什么存在动态内存分配

2. 动态内存函数的介绍

2.1 malloc和free

2.2  calloc

2.3  realloc


今天要和大家分享的内容是的动态内存管理,我们先从他的定义入手学习。

1.为什么存在动态内存分配

我们到现在已经掌握了内存开辟的方式就是要么创建一个变量,要么就创建一个数组

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

 如上所示,创建一个变量,就是在栈空间上开辟四个字节;创建一个char类型的数组里面有十个元素,就是在栈空间上开辟10个字节的连续的空间;

那我们目前学到的开辟空间的方法只有这两种方法;

这两种方法创建的空间大小是固定的,int类型就是四个字节,char类型的数组就是十个字节,不能将其增大也不能减小。所以这种方式在某些环境下是有一定的局限性的,所以为了让我们能更加灵活的控制我们所需要的内存的空间的大小,这时我们就需要学习C语言中动态内存管理的功能,

也会涉及到其中的一些相关的函数,接下来就对这些函数进行分析。

2. 动态内存函数的介绍

2.1     malloc和free

先在官方的网站上大概了解一下它的内容

malloc本质上是一个函数,使用时要包含<stdlib.h>的头文件,返回类型是人任意类型的指针,参数是 size_t类型,也就是无符号整形, 他的作用是在内存中申请数个字节的内存块,返回一个指针指向这个内存块的起始位置。

上代码简单使用一下这个函数

int main()
{
	int* p =(int*) malloc(20);
	return 0;
}

那么这段代码什么意思呢?

假设我们要开辟二十个字节的空间,我们就在malloc后面的括号中输入想要开辟的空间,注意单位是字节;

再来观察一下malloc函数的返回值,是一个任意类型的指针,我们在使用时要将其具体细化成我们想要的数据,那这里我们举一个例子,不妨将其定义为整形指针,所以也要给malloc函数开辟的空间的类型强制转化成int类型(我们所要使用的类型)

那以上就是malloc开辟空间的简单使用。

接下来在深度研究之前要了解一些知识

上图是我们计算机内存划分的简单的示意图,这里要为大家说明的是我们所创建的临时变量,和函数的形式参数以及具有临时性的变量都放在栈区;

我们今天索要讲解的动态内存管理的函数所申请的内存空间都在堆区中,可以看到上图有很多动态内存管理的函数,都会一一讲解;

最后就是静态区,静态区所存放的就是static所修饰的静态变量和全局变量;

了解完这些我们再回头看我们的malloc函数

 我们使用malloc申请空间的时候要注意,malloc申请空间可能因为空间不足而导致申请失败,

而申请失败时就会返回一个空指针,所以我们在使用malloc函数时要对其进行判断;

 代码如下

int main()
{
	int* p =(int*) malloc(20);
	if (p = NULL)
	{
		printf("%s\n",strerro(errno));
        return 1;
	}

上面说过申请失败会返回一个空指针,我们不妨对其进行判断 :

如果p是空指针的话,就打印出他错误的原因,打印分析出错的原因后就利用return 1结束当前的程序,因为已经申请空间失败了,不能再往下走了,相信大家不难看懂

使用malloc函数申请空间后,要使用free来释放空间,

那free是怎样释放空间的呢?

 可以看到free的函数的参数是一个任意类型的指针,也就是说使用free释放空间的时候,只需要将malloc申请的空间的首地址传进free函数中即可。

所以上面的代码中,malloc申请空间的首地址赋值给了指针变量p,所以我们将p传入free中即可

接下来将代码调试起来

 可以看到malloc开辟的空间的首地址已经赋给了p;

 这时free函数已经释放了p指针指向的空间,

但是这时我们要注意到是虽然空间已经得到了释放,但我们可以看到指针p指向了一块不知道的空间,这时p就成了一块野指针,所以我们要在最后将空指针赋予p

 这时我们就看到p已经忘记了当时申请的20个字节的空间在哪

这时候就避免了野指针的问题; 

申请空间和释放空间都研究过后我们再来看一看如何使用这块空间;

使用也非常的简单,既然我们申请的时一块连续的空间,我们不妨用数组来将其应用:

 对申请的二十个空间进行访问我们可以利用数组的遍历对其访问,并将其打印出来以便我们观察

可以看到使用其申请的空间没有那么困难。

2.2    calloc

 可以看到calloc函数的返回值是一个任意类型的指针,函数的两个参数都是size_t类型的数。

下面的对函数功能的描述是说开辟并且用'0'初始化数组。

用代码简单使用calloc函数:

int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		printf("calloc()--%s", strerror(errno));
		return 1;
	}
	//使用
	 int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放
	free(p);
	p = NULL;
	return 0;

}

可以看到使用对其开辟的空间时只是将其打印出来

 运行的结果就是将其开辟的内容全都初始化为0;

那calloc和malloc有何区别呢?

1.参数不同

malloc只有一个参数,要开辟多少字节的空间直接在后面的括号内告诉他就好;

calloc有两个参数,要确定多少和一个数据多大的参数,类似于数组的开辟方式;

2.两者都是在堆区上申请空间,但是malloc不初始化,calloc会初始化为0;

如果要初始化就选择calloc,不需要初始化就选择malloc,具体要看需求使用 。

malloc在返回地址之前没有初始化,而calloc在返回地址之前要全部初始化为0,所以calloc的效率是比malloc要低的

2.3     realloc

继续观察官方网站对这个函数的定义

 它的返回类型是任意类型的指针,函数的一个参数是任意类型的指针,另一个数size_t的无符号整型的整数;

对函数功能的描述就是改变内存块的大小

realloc函数的作用就是 让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小
的调整

我们直接用代码简单探究一下

 我们可以看到这里再使用空间中增加了realloc的使用,这个意思就是说在指针p指向的首地址处,向后开辟40个字节的空间(注意这里开辟空间的字节大小要是加上之前空间的总大小)

使用这段空间也有两种方法,下面为大家用图例演示会更加便于理解

第一种情况:

 

 这种增加内存的情况是在原先的内存空间中续上一段内存(篮筐表示realloc函数续上的内存)。这种情况下是因为本身申请的空间不会受到别的内存的干扰。

第二种情况:
 

 上图这种情况时我们发现原本的空间后面,已经没有我们能够所再申请加长的空间了,这时候realloc就会重新申请一块更大的空间空间在别的地方,同时它会将之前空间的内容拷贝到新的空间中,这块空间一定满足你所开辟的空间,同时也会释放旧的空间,然后返回新空间的地址。

这就是realloc函数使用时内部会发生的情况;

但是要注意的是两种情况放回的地址不一样,第一种情况会返回旧的地址,第二种情况会返回新的地址。当然还要想到的一种情况可能会返回空指针,万一realloc在申请空间失败的情况下就会返回空指针,这点也必须考虑到,所以我们必须要用一个临时指针来存放他申请的空间,并且加以判断

使用例子如下

int*ptr=(int*)realloc(p, 40);
	if (ptr!=NULL)
	{
		p = ptr;
	}
    else
    {
        printf("%s\n",strerror(errno));
        return 1;
    }

先使用realloc函数开辟空间,并将其首地址存入一个临时的指针变量,并加以判断,如果不是空指针,就交给原先的p指针对这块空间进行管理。

如果真的开辟空间失败的话,就结束当前进程。

那接下来就给大家调试起来,让大家看看是否真的会存放在不同的空间

 我们可以看到两个指针的指向的地址是同样的,

那怎么样才会不一样呢?

我们只需要将realloc申请的空间增大

 就可以看到两者的地址已经不一样了,这就是realloc在申请空间时的第二种情况。

以上就是索要分享的动态内存管理的函数的要分享的内容,下次会给大家分享常见的动态内存的错误。

如果这篇文章对你有所帮助,也请三连多多支持一下,感谢阅读。

猜你喜欢

转载自blog.csdn.net/wangduduniubi/article/details/129756824