我们先回顾一下已经学习过的开辟内存空间的两种方法:
int a = 0;//在栈区上开辟四个字节的空间
char ch[10] = { 0 };//在栈区上开辟十个字节的连续空间
这种开辟空间的方式有两个特点:
1. 开辟的空间是固定的;
2. 数组在声明时,必须指定数组的长度,并且在程序编译期间为其分配内存空间。
但是呢 对于空间的需求这两种方式远远不能满足。有些时候我们需要使用的空间大小需要在程序运行时才知道。比如我的上一篇博客:C语言实现通讯录(静态版) 中,我们的通讯录大小是固定的。
通讯录大小是100,即最多只能存100个联系人的信息,那如果我们在使用时,需要存200人甚至更多人呢? 那这个程序就不能满足我们的需求了。 而需要解决这个问题,我们就得学会使用C语言动态内存分配函数,对通讯录的大小进行动态分配。
使用函数需要引头文件:
C语言提供了三个动态内存分配函数:malloc
,calloc
,realloc
。并且需要注意的是这三个函数都是在堆区申请空间,而在堆区申请的空间不会像在栈区申请的空间那样自动释放内存,因此C语言还提供了一个free
函数用于手动释放堆区开辟的内存空间,以免发生内存泄漏
。
malloc——void* malloc (size_t size);
工作原理:
堆区
中申请一块连续的空间,空间大小为size
单位为字节。NULL
指针。所以在使用malloc函数后一定要对返回值进行检查。viod*
,需要我们自己对其进行强制类型转换。size
的大小为0,这一行为是C语言标准为定义的,取决于编译器。使用案例:
#include
#include
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函数会指向一个已经不属于我们的空间,导致越界访问等问题。
calloc——void* calloc (size_t num, size_t size);
工作原理:
#include
#include
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函数有比较好的一点就是方便初始化。
realloc——void* realloc (void* ptr, size_t size);
工作原理:
free——void free (void* ptr);
工作原理:
即我们可以得到释放内存空间的代码写法:
free(ptr);
ptr=NULL;
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
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个元素,就会造成越界访问。
这一点我们之前已经说过了,空间不是动态开辟的,那free函数的行为是未定义的。
void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
其实和3差不多,代码如下:
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏
动态开辟的内存空间不会自动释放,只有在程序结束时才会释放。所以不及时释放的话,会出现程序一直在吃内存的情况。
所以切记:动态开辟的空间一定要释放,并且正确释放
学会动态分配内存让我们在编写程序的时候更加灵活。通过上面的学习。我想现在可以解决上一期博客中的通讯录只有固定大小的问题了吧。我将在后期的博客中给出教程,即动态分配大小的通讯录,并且可以进行文件操作。期待一下吧!