1.为什么要有动态内存
我们现在掌握的内存开辟方法有:
int val = 20;//在栈空间开辟4个字节
char str[10]={0};//在栈空间开辟10个字节的连续的空间
但是上述的方式有两个点要注意:
1.空间开辟的大小是固定的
2.数组在申明的时候,一定要指定数组的长度(就算是变长数组也是先给变量赋值,再根据变量的大小,来开辟空间),数组空间一旦确定了大小是不能调整的
但是我们在实际上对于内存的需求方式,绝不仅仅是上述的情况。有时候我们需要的空间大小是在程序运行的时候才能知道,数组是在编译时开辟空间的,这是就不能满足需求了。
这时C语言就引入了动态内存开辟,让程序员可以自己根据需求来申请和释放空间这样就比较灵活。
2.动态内存函数
要想学会动态开辟内存就必须掌握这四个函数malloc
free
calloc
realloc
。
它们的头文件都为<stdlib.h>
他们开辟空间的大小单位都为字节。
2.1 malloc
函数原型:void* malloc(size_t size);
该函数向内存申请一块连续何用的空间,并返回指向这块空间的指针。
- 如果开辟成功,返回一个指向开辟好空间的指针。
- 如果开辟失败,则会返回一个
NULL
指针,因此malloc的返回值一定要做检查。
因为返回类型是void*
,所以malloc
函数并不知道开辟空间时是什么类型,在实际使用时类型是有使用者决定的。
如果参数size为 0,malloc的行为是标准未定义的,结果取决于编译器。
代码如下:
#include<stdio.h>
#include<stdilb.h>
int main()
{int* parr = (int*)malloc(10 * sizeof(int));if (parr == NULL){perror("malloc");exit(1);}for (int i = 0; i < 10; i++){//初始化*(parr + i) = i + 1;//打印printf("%d ", *parr + i);}return 0;
}
注意:malloc开辟的空间并未进行初始化,里面是随机值
#include<stdio.h>
#include<stdilb.h>
int main()
{int* parr = (int*)malloc(10 * sizeof(int));if (parr == NULL){perror("malloc");exit(1);}for (int i = 0; i < 10; i++){//初始化*(parr + i) = i + 1;//打印printf("%d ", *parr + i);}return 0;
}
#include<stdio.h>
#include<stdilb.h>
int main()
{int* parr = (int*)malloc(10 * sizeof(int));if (parr == NULL){perror("malloc");}for (int i = 0; i < 10; i++){//未进行初始化//打印printf("%d ", *parr + i);}return 0;
}
结果如下:
这些动态开辟的空间是存放在哪里呢?
如图:
2.2 free
申请了空间在使用完的时候肯定是要还回去的嘛,那怎么还呢?
C语言提供了另外一个函数free,这是专门用来做动态内存的释放和回收的,函数原型如下:
void free(void* ptr)
- 如果参数
ptr
指向的空间不是动态内存的时候,free函数的行为是未被定义的 - 如果参数
ptr
是NULL
指针,那么free函数什么都不会做
注意:传递给free函数的参数是要被释放空间的起始地址
代码如下:
#include<stdio.h>
#include<stdlib.h>
int main()
{int* parr = (int*)malloc(10 * sizeof(int));if (parr == NULL){perror("malloc");}for (int i = 0; i < 10; i++){//初始化*(parr + i) = i + 1;//打印printf("%d ", *parr + i);}//只用完动态申请的空间要进行释放free(parr);parr = NULL;return 0;
}
free仅仅是将空间的使用权限还给了操作系统;
但parr还指向原来的地址,这就成为野指针;
为了避免成为野指针,及时将parr置为NULL。
如果一直不用函数free
来释放申请的空间,这样很可能会造成内存泄漏!!!
2.3 calloc
C语言还提供了一个函数叫calloc
,calloc
函数也用来动态内存分配。原型如下:
void * calloc(size_t num, size_t size);
- 函数的功能是将
num
个大小为size
的元素开辟一块空间,并且会将开辟空间的每个字节初始化为0。 - 函数
calloc
和函数malloc
的区别就是calloc
会在返回地址之前,把申请空间的每个字节初始化为0。
由于函数malloc
比函数calloc
少一步(将内存初始化),所以malloc
比calloc
更快,如果更追求效率的话可以使用malloc
,如果不想自己初始化的话可以使用calloc
。
代码如下:
int main()
{int* pca = (int*)calloc(10,sizeof(int));if (pca == NULL)//判断是否开辟失败{perror("calloc");exit(1);//直接退出程序}printf("calloc: ");for (int i = 0; i < 10; i++){printf("%d ", *pca + i);}printf("\n\n");int* pma = (int*)malloc(10 * sizeof(int));if (pma == NULL){perror("malloc");exit(1);}printf("malloc: ");for (int i = 0; i < 10; i++){printf("%d ", *pma + i);}return 0;
}
结果如图:
2.4 realloc
realloc
函数能让动态内存管理更加灵活。
有些时候我们会发现我们申请的空间太小了,有时候又感觉申请的空间太大了,那么为了合理的使用空间,我们就会对申请的空间大小进行灵活的调整,那么realloc
函数就能做到对动态空间大小的调整。
函数原型如下:
void* realloc(void* ptr, size_t size)
ptr
是要被调整的内存地址size
是调整之后该内存的大小- 返回值为调整之后的内存空间的起始位置
2.4.1 realloc在调整内存空间有三种情况
- 原空间后面有足够的空间用来调整。
- 原空间后面没有足够空间用来调整,扩展的方法是:在堆空间上另找⼀个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
- 空间调整失败,返回
NULL
(这是最坏的情况)
如图所示
代码如下:
#include<stdio.h>
#include<stdlib.h>int main()
{//开辟int* parr = (int*)malloc(5 * sizeof(int));if (parr == NULL){perror("malloc");}//使用for (int i = 0; i < 5; i++){*(parr + i) = i + 1;}//假设要多插入5个int类型的数据//这时空间就不够了,需要调整int* parr = (int*)realloc(parr, 10 * sizeof(int));//上述代码合适吗//不合适,因为万一调整失败,会返回NULL//这时我原来开辟的空间就找不到了//所以正确的方法应该如下//先申请调整空间int* ptr = (int*)realloc(parr, 10 * sizeof(int));//调整成功if (ptr != NULL){//将申请的空间地址赋给parr(如果是情况二,parr的原空间已经被释放了)parr = ptr;}else{perror("realloc");}//处理要求……free(parr);return 0;
}
上述代码int* parr = (int*)realloc(parr, 10 * sizeof(int));
代码合适吗?
完全不合适!!! 因为万一调整失败,会返回NULL;
这时我原来开辟的空间就找不到了。
所以正确的realloc
使用方式是先创建一个指针变量来接收开辟的空间,如果判断ptr
是否开辟成功,如果开辟成功。将ptr
赋给parr
。
如果开辟失败parr
指向的原空间也能继续使用。
补充:realloc
也可以完成malloc的功能;
如果传的是NULL
指针,realloc
就会开辟一块新空间。
3.常见的动态内存的错误
3.1对NULL指针的解引用
具体如下:
#include<stdio.h>
#include<stdlib.h>int main()
{int* ptr = (int*)malloc(INT_MAX);*ptr = 20;//如果ptr是NULL,就会有问题return 0;
}
解决方法:开辟空间后,要第一时间判断是否空
3.2对动态开辟空间进行越界访问
具体如下:
#include<stdio.h>
#include<stdlib.h>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);
}int main()
{test();return 0;
}
解决方法:控制好访问的范围
3.3对非动态开辟空间使用free释放
具体如下:
#include<stdio.h>
#include<stdlib.h>int main()
{int a = 0;int* p = &a;//处理……free(p);//这样行吗,肯定不行!!!//a是局部变量,他开辟的空间是放在栈区的,而不是堆区的//free释放的是realloc、malloc、calloc开辟的空间(动态内存空间是在堆区的)return 0;
}
//这样行吗,肯定不行!!!
//a是局部变量,他开辟的空间是放在栈区的,而不是堆区的
//free释放的是realloc、malloc、calloc开辟的空间(动态内存空间是在堆区的)
编译都无法编译过去
解决方法:不要对非动态开辟空间进行释放!!!
3.4使用free释放的并不是动态开辟空间的起始位置
具体如下:
#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置return 0;
}
同样也无法编译过去
解决方法:传给free的时候确保是空间的起始地址
3.5对同一块内存进行对此释放
具体如下:
#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(100);free(p);free(p);return 0;
}
同样还是无法编译过去
解决方法:在第一次释放后及时的给p赋NULL
#include<stdio.h>
#include<stdlib.h>int main()
{int* p = (int*)malloc(100);free(p);//解决方法p = NULL;free(p);return 0;
}
前面也说过了,如果传给free
函数的是NULL
,free
不会进行如何的操作。
3.6忘记释放动态开辟空间(内存泄漏)
具体如下:
void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();//出来后test就被销毁了,就无法找到p所指向的空间了while (1);
}
忘记释放不再使用的空间会造成内存泄漏
释放动态内存有两个方法:
-
在不用的时候使用
free
函数进行释放 -
如果一直没有被
free
释放,当程序运行结束后,会由操作系统来回收
解决方法:
1.谁申请的空间谁释放,如果在函数里,那么在出函数前记得释放malloc/calloc/realloc
要和free
成对出现。2.如果不能释放(后续会用到),要告诉下一个使用者,记得释放动态开辟空间。
切记:动态开辟的空间⼀定要释放,并且正确释放。
结语
最后感谢您能阅读完此片文章,如果有任何建议或纠正欢迎在评论区留言。如果您认为这篇文章对您有所收获,点一个小小的赞就是我创作的巨大动力,谢谢!!!