C语言动态内存分配函数

为什么存在动态内存分配

我们先回顾一下已经学习过的开辟内存空间的两种方法:

	int a = 0;//在栈区上开辟四个字节的空间
	char ch[10] = {
    
     0 };//在栈区上开辟十个字节的连续空间

这种开辟空间的方式有两个特点:
1. 开辟的空间是固定的;
2. 数组在声明时,必须指定数组的长度,并且在程序编译期间为其分配内存空间。

但是呢 对于空间的需求这两种方式远远不能满足。有些时候我们需要使用的空间大小需要在程序运行时才知道。比如我的上一篇博客:C语言实现通讯录(静态版) 中,我们的通讯录大小是固定的。
在这里插入图片描述
通讯录大小是100,即最多只能存100个联系人的信息,那如果我们在使用时,需要存200人甚至更多人呢? 那这个程序就不能满足我们的需求了。 而需要解决这个问题,我们就得学会使用C语言动态内存分配函数,对通讯录的大小进行动态分配。

动态内存分配函数介绍

使用函数需要引头文件:<stdlib.h>
C语言提供了三个动态内存分配函数:malloccallocrealloc。并且需要注意的是这三个函数都是在堆区申请空间,而在堆区申请的空间不会像在栈区申请的空间那样自动释放内存,因此C语言还提供了一个free函数用于手动释放堆区开辟的内存空间,以免发生内存泄漏

1. malloc

malloc——void* malloc (size_t size);
工作原理:

  1. malloc函数会向内存的堆区中申请一块连续的空间,空间大小为size单位为字节。
  2. 如果内存开辟成功,则函数返回开辟空间起始位置的地址;否则返回NULL指针。所以在使用malloc函数后一定要对返回值进行检查。
  3. malloc函数返回的指针类型为viod*,需要我们自己对其进行强制类型转换。
  4. 如果传入参数size的大小为0,这一行为是C语言标准为定义的,取决于编译器。

使用案例:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    
    
	int num = 0;
	scanf("%d", &num);
	//int arr[num] = { 0 };error

	//使用动态内存分配
	int* ptr = NULL;
	ptr = (int*)malloc(num * sizeof(int));
	if (ptr != NULL)//检查函数的返回值是否正常
	{
    
    
		//使用空间
		for (int i = 0; i < num; i++)
		{
    
    
			*(ptr + i) = i;
		}
		for (int i = 0; i < num; i++)
		{
    
    
			printf("%d ", *(ptr + i));
		}
	}
	//释放内存空间并将ptr指针置空
	free(ptr);
	ptr = NULL;

	return 0;
}

程序运行结果:
在这里插入图片描述
需要注意的就是最后使用free函数将空间释放后,我们还需要将ptr函数置空,否则ptr函数会指向一个已经不属于我们的空间,导致越界访问等问题。

2. calloc

calloc——void* calloc (size_t num, size_t size);
工作原理:

  1. calloc函数会开辟num个大小为size字节的空间,并且将空间的每个字节全部赋值为0.
  2. calloc函数与malloc函数的区别就在于calloc函数会将开辟的空间主动赋值为0.
    使用案例:
#include <stdio.h>
#include <stdlib.h>
int main()
{
    
    
	int num = 0;
	scanf("%d", &num);
	//int arr[num] = { 0 };error

	//使用动态内存分配
	int* ptr = NULL;
	ptr = (int*)calloc(num, sizeof(int));
	if (ptr != NULL)//检查函数的返回值是否正常
	{
    
    
		//使用空间
		for (int i = 0; i < num; i++)
		{
    
    
			printf("%d ", *(ptr + 1));
		}
		printf("\n");
		for (int i = 0; i < num; i++)
		{
    
    
			*(ptr + i) = i;
		}
		for (int i = 0; i < num; i++)
		{
    
    
			printf("%d ", *(ptr + i));
		}
	}
	//释放内存空间并将ptr指针置空
	free(ptr);
	ptr = NULL;

	return 0;
}

程序运行结果:
在这里插入图片描述
可以看到确实主动赋值为0了。我个人觉得calloc函数有比较好的一点就是方便初始化。

3. realloc

realloc——void* realloc (void* ptr, size_t size);
工作原理:

  1. ptr代表需要修改的空间的首地址。
  2. size代表修改过后的内存空间大小
  3. realloc函数返回修改过后的首元素地址
    注意:realloc函数修改内存空间有两种情况
    1.如果需要扩展的内存后面的空间足够大的话,直接在该空间后面开辟空间
    2.如果需要扩展的内存后面没有足够空间的话,函数会重新找一块合适的空间来存放新的空间。
    图解:
    在这里插入图片描述

4. free

free——void free (void* ptr);
工作原理:

  1. free将ptr所指向的内存空间释放。
  2. 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  3. 如果参数 ptr 是NULL指针,则函数什么事都不做。
  4. free函数只负责释放内存空间,并没有对ptr指针进行修改,所以在调用完free函数会,应该将ptr指针置空,以免造成非法访问。

即我们可以得到释放内存空间的代码写法:

free(ptr);
ptr=NULL;

一些动态内存分配的错误案例

1. 对NULL指针的解引用操作

void test()
{
    
    
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}

在这里插入图片描述

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

void test()
{
    
    
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
    
    
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
    
    
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}

越界访问无论是在栈区还是堆区都是不对的。这里malloc函数只申请了40个字节的空间,能存放10个整型大小的变量,而i==10时,其实是第11个元素,就会造成越界访问。

3. 对非动态开辟内存使用free释放

这一点我们之前已经说过了,空间不是动态开辟的,那free函数的行为是未定义的。

void test()
{
    
    
int a = 10;
int *p = &a;
free(p);//ok?
}

程序运行错误
在这里插入图片描述

4. 使用free释放一块动态开辟内存的一部分

void test()
{
    
    
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}

同理也是程序出错,而且在vs2022中也没有对该行为的定义
在这里插入图片描述

5. 对同一块动态内存多次释放

其实和3差不多,代码如下:

void test()
{
    
    
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}

在这里插入图片描述

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

void test()
{
    
    
int *p = (int *)malloc(100);
if(NULL != p)
{
    
    
*p = 20;
}
}
int main()
{
    
    
test();
while(1);
}

忘记释放不再使用的动态开辟的空间会造成内存泄漏
动态开辟的内存空间不会自动释放,只有在程序结束时才会释放。所以不及时释放的话,会出现程序一直在吃内存的情况。
所以切记:动态开辟的空间一定要释放,并且正确释放

总结

学会动态分配内存让我们在编写程序的时候更加灵活。通过上面的学习。我想现在可以解决上一期博客中的通讯录只有固定大小的问题了吧。我将在后期的博客中给出教程,即动态分配大小的通讯录,并且可以进行文件操作。期待一下吧!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_72482689/article/details/127138770