C语言动态内存分配函数
为什么存在动态内存分配
我们先回顾一下已经学习过的开辟内存空间的两种方法:
int a = 0;//在栈区上开辟四个字节的空间
char ch[10] = {
0 };//在栈区上开辟十个字节的连续空间
这种开辟空间的方式有两个特点:
1. 开辟的空间是固定的;
2. 数组在声明时,必须指定数组的长度,并且在程序编译期间为其分配内存空间。
但是呢 对于空间的需求这两种方式远远不能满足。有些时候我们需要使用的空间大小需要在程序运行时才知道。比如我的上一篇博客:C语言实现通讯录(静态版) 中,我们的通讯录大小是固定的。
通讯录大小是100,即最多只能存100个联系人的信息,那如果我们在使用时,需要存200人甚至更多人呢? 那这个程序就不能满足我们的需求了。 而需要解决这个问题,我们就得学会使用C语言动态内存分配函数,对通讯录的大小进行动态分配。
动态内存分配函数介绍
使用函数需要引头文件:<stdlib.h>
C语言提供了三个动态内存分配函数:malloc
,calloc
,realloc
。并且需要注意的是这三个函数都是在堆区申请空间,而在堆区申请的空间不会像在栈区申请的空间那样自动释放内存,因此C语言还提供了一个free
函数用于手动释放堆区开辟的内存空间,以免发生内存泄漏
。
1. malloc
malloc——void* malloc (size_t size);
工作原理:
- malloc函数会向内存的
堆区
中申请一块连续的空间,空间大小为size
单位为字节。 - 如果内存开辟成功,则函数返回开辟空间起始位置的地址;否则返回
NULL
指针。所以在使用malloc函数后一定要对返回值进行检查。 - malloc函数返回的指针类型为
viod*
,需要我们自己对其进行强制类型转换。 - 如果传入参数
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);
工作原理:
- calloc函数会开辟num个大小为size字节的空间,并且将空间的每个字节全部赋值为0.
- 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);
工作原理:
- ptr代表需要修改的空间的首地址。
- size代表修改过后的内存空间大小
- realloc函数返回修改过后的首元素地址
注意:realloc函数修改内存空间有两种情况
1.如果需要扩展的内存后面的空间足够大的话,直接在该空间后面开辟空间
2.如果需要扩展的内存后面没有足够空间的话,函数会重新找一块合适的空间来存放新的空间。
图解:
4. free
free——void free (void* ptr);
工作原理:
- free将ptr所指向的内存空间释放。
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做。
- 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);
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏
动态开辟的内存空间不会自动释放,只有在程序结束时才会释放。所以不及时释放的话,会出现程序一直在吃内存的情况。
所以切记:动态开辟的空间一定要释放,并且正确释放
总结
学会动态分配内存让我们在编写程序的时候更加灵活。通过上面的学习。我想现在可以解决上一期博客中的通讯录只有固定大小的问题了吧。我将在后期的博客中给出教程,即动态分配大小的通讯录,并且可以进行文件操作。期待一下吧!