【C语言】小王带您轻松实现动态内存管理(简单易懂)
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
在上文通讯录制作中动态通讯录的使用中就用到了动态内存管理如果有同学想看一看是如何运用的内存管理函数的请参考这篇文章接下来我们一起学习动态内存管理的相关知识。【C语言】使用C语言实现静态、动态的通讯录简单易懂_小王学代码的博客-CSDN博客
目录
前言
我们已经掌握的内存开辟的方法有两种
int a = 10 //在栈空间上开辟4个字节的空间
int a[10] = {0}; //在栈空间上开辟40个字节的连续空间
这些开辟方式都有两个共同的特点
1.空间开辟大小是固定的
2.数组在申明的时候必须指定数组的长度它需要的内存在编译的时候分配
我们为什么要实现动态管理内存呢这又什么作用呢
我们对于空间的需求不仅仅只是上面两种有时候我们到底需要多少空间需要运行之后才能知道这个时候就需要动态开辟内存空间即动态内存函数就诞生了
一、动态内存函数有那些
1.malloc和free
2.calloc
3.realloc
1.1 malloc和free
malloc是C语言提供的一个动态内存开辟的函数
这个函数向内存申请一块连续可用的空间并返回指向这块空间的指针。
1.如果开辟成功则返回一个指向开辟好空间的指针。
2.如果开辟失败则返回一个 NULL 指针因此 malloc 的返回值一定要做检查。3.返回值的类型是 void* 所以 malloc 函数并不知道开辟空间的类型具体在使用的时候使用者自己来决定。4.如果参数 size 为 0 malloc 的行为是标准是未定义的取决于编译器。
void*的返回类型使用的时候根据情况强制类型转换
C语言还提供free函数专门是用于做动态内存的释放和回收的函数原型如下
free函数是用来释放动态开辟的内存
1.如果参数 ptr 指向的空间不是动态开辟的那free函数的行为是未定义的。会报错
2.如果参数 ptr 是NULL指针则函数什么事都不做。
图文演示
头文件要加上 malloc.h
代码演示
int main()
{
int num = 0;
scanf("%d", &num);
//int arr[num] = { 0 }; num 在 [] 中
//VS 不支持这样但是可以使用动态内存函数实现动态数组
int* ptr = (int*)malloc(sizeof(int) * num);
if (NULL == ptr) {//进行判断是否创建成功
perror("malloc::ptr");
}
else {
for (int i = 0; i < 10; i++) {
*(ptr + i) = i;
}
for (int i = 0; i < 10; i++) {
printf("%d ", *(ptr + i));
}
free(ptr); //使用free函数释放动态申请的ptr
ptr = NULL; //将ptr free之后置为NULL防止野指针非法访问
}
return 0;
}
而且malloc函数创建的空间不会进行初始化里面存放的是随机值如图
1.2 calloc
calloc函数也是C语言提供的用来动态内存分配原型如下
calloc函数介绍
1.函数的功能是为 num 个大小为 size 的元素开辟一块空间并且把空间的每个字节初始化为 0。2.与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0。
实操图文分析
代码演示
int main()
{
int num = 0;
int* ptr = (int*)calloc(10, sizeof(int));//使用calloc函数
for (int i = 0; i < 10; i++) {
*(ptr + i) = i;
}
for (int i = 0; i < 10; i++) {
printf("%d ", *(ptr + i));
}
free(ptr);//free 动态申请的ptr
ptr = NULL;//置为NULL防止野指针越界访问
return 0;
}
对于calloc动态申请的空间是否每一个字节都变为0呢我们来看下图
这也是calloc和malloc函数的最大的区别是否自动初始化前者有后者无
1.3 realloc
realloc也是C语言提供的动态内存申请函数使得动态内存管理更加灵活。本质是可以对已经动态申请过的空间进行增容是更加灵活的。
有时会我们发现过去申请的空间太小了有时候我们又会觉得申请的空间过大了那为了合理的时 候内存我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小 的调整。
函数原型如下并对两个形参ptr和size进行分析
如上图
1.ptr可以为NULL相当于malloc一个新的空间ptr是要调整的内存地址
2.size同样可以为0则返回值取决于特定的库实现它可能是空指针也可能是不应取消引用的其他位置。size是调整之后的大小
3.返回值为调整之后的内存起始位置。
4.这个函数调整原内存空间大小的基础上还会将原来的数据移动到新空间。
1.3.1 realloc调整内存空间的时候有两种情况
第一种情况当原有空间之后的内存空间足够的时候
第二种情况当原有空间之后的内存空间不够时
如图所示
因为这两种情况是随机发生的不能控制必须使用哪一种所以我们就要小心一个事情不要用原来动态开辟的变量ptr来直接接收realloc应该创建临时变量接收先判空之后再赋值给ptr
代码图示
可以自行测试
int main()
{
int* p = (int*)malloc(sizeof(int)*10);
if (p == NULL) {
perror("malloc::p");
}
else {
printf("%p\n", p);
}
int* ptr = (int*)realloc(p, sizeof(int) * 20);//创建临时变量
//如果使用 int* p = (int*)realloc(p,....这样的话如果创建失败返回NULL
//这样的话p的内容就没有了所以创建临时变量ptr然后下面判空之后可以交换
if (NULL == ptr) {
perror("realloc::ptr");
}
else {
p = ptr;
ptr = NULL;
printf("%p\n", p);
}
free(p);
p = NULL;
return 0;
}
二、常见动态内存错误案例分析
2.1 对于NULL指针的解引用操作
意思就是要学会使用动态内存函数的时候吗要进行判空不然谁知道有没有问题NULL
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
*p = 10;//这个时候谁知道p是不是NULL如果是NULL那么这就是非法访问是错误
free(p);
return 0;
}
2.2 对动态开辟空间的越界访问
就是说开辟多少空间就是多少空间不能越过这个字节数的界限访问空间外的地址
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
if (NULL == p) {
perror("malloc::p");
}
else {
for (int i = 0; i < 100; i++) {
*(p + i) = i + 1;//当i等于10的时候就开始越界访问
}
for (int i = 0; i < 11; i++) {
printf("%d ", *(p + i));
}
free(p);
p = NULL;
}
return 0;
}
和数组一样不要越界不需要多想什么额外的东西
2.3 对非动态开辟内存使用free释放
free可以放置NULL进去不会报错但是不能放非动态开辟的内存会报错
图示分析free函数
代码演示
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
int a = 10;
free(&a);//非动态内存开辟的会报错
//free(NULL); //没有什么反应程序正常
return 0;
}
2.4 使用free释放了动态开辟内存的一部分
就是说如果动态开辟内存之后的p指针的位置发生改变的话再去释放free(p)只是释放一部分
代码演示
//举例
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
p++;
free(p);//这个的时候p向右移动一个整型字节空间再进行释放那么先前那个空间就没被释放
return 0;
}
2.5 对同一块动态内存进行多次释放
多次释放会报错的
图示
2.6 动态开辟空间忘记释放内存泄漏
所以我们要养成当一个动态空间不用的时候就free他放置内存泄露
代码演示
int main()
{
//test();
while (1) {
malloc(1);//一直申请就是不释放
}
}
三、练习题
3.1 第一个
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
//改为传递地址就可以或者就是用str接收
//str= GetMemory(str);//实际上用临时变量接收更好
strcpy(str, "hello world");
printf(str);
//用完释放
//free(str);
//str=NULL;
}
1.传值操作就算p申请了空间也不会使得str发生改变所以str依旧是NULL不能有strcpy
2.内存泄漏 GetMemory(str);未释放p的空间
3.2 第二个
char *GetMemory(void)
{
//修改为
//static char p[] = "hello world";
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
典型的返回栈地址问题p数组是局部变量 确实是返回了p的地址给str但是GetMemory函数结束之后数组p的空间就没有再访问p的地址printfstr就会非法访问
3.3 第三个
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
//修改为free(str);
//str=NULL;
}
没有释放str动态开辟的空间没有freestrstr=NULL
3.4 第四个
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
//修改意见
//str=NULL;
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
在使用str之前就释放了str申请的空间释放之后str=NULL保留原来地址str这个时候已经是野指针了因为没有了对相应空间的访问权限之后确实是输出了world但是从if语句就已经错误了置为str=NULL 就可以了
总结
本文主要是对于malloc、calloc、realloc、free函数的介绍和使用细节的说明还有一些关于动态内存管理的函数学会了这些对于以后数据结构的内容会更加得心应手所以希望大家能多多支持接下来下一章我们跟大家讲解一下文件管理的内容。学会了就可以更新通讯录啦