动态内存管理柔性数组
动态内存管理
我们熟知的内存开辟:
int a=20;//在栈空间开辟四个字节
char arr[10]={0};//在栈空间连续开辟10个字节
但其有个缺点就是开辟大小固定;数组空间一旦确定了就不能调整。
这就不得不提到动态内存的开辟了
1.动态内存的开辟和释放:malloc和free
都需要包含头文件<stdlib.h>
malloc
void* malloc(size_t size);
功能:向内存的栈区申请一块连续可用的空间,并返回指向这块空间的起始地址
开辟成功,返回这块空间起始地址;开辟失败,返回一个NULL指针,所以其返回值需认真检查 。
开辟示例:
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(5 * sizeof(int));//申请20个字节的空间,存5个整数//注意有时候开辟的空间过大可能会开辟失败if (p == NULL){perror("malloc");//打印错误信息return 1;}//开辟成功//使用这块空间int i = 0;for (i = 0;i < 5;i++){*(p + i) = i + 1;//p[i]=i+1;}for (i = 0;i < 5;i++){printf("%d ", p[i]);}return 0;
}
free
当开辟的内存使用完后需要还回去,这就涉及到了动态内存的释放和回收,这里有一个专门的函数——free
void free(void* ptr);
- 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
- 如果参数ptr是NULL指针,则函数什么事都不用做
同malloc所例代码,使用free还空间
2.动态内存分配及扩容:calloc和realloc
calloc
calloc 是动态内存分配的
void* calloc(size_t num,size_t size);
- 函数功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
- 与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节全部初始化为0
例:
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)calloc(10 * sizeof(int));if (p == NULL){perror("calloc");return 1;}int i = 0;for (i = 0;i < 10;i++){printf("%d\n", p[i]);}free(p);p = NULL;return 0;
}
realloc
动态内存开辟好了有时需要对其大小进行调整,那么realloc函数就可实现
void* realloc(void* ptr,size_t size);
-
ptr是要调整的内存地址;size调整之后新大小,单位是字节;返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间 -
realloc在调整内存空间时存在两种情况:
(1)原有空间之后有足够大的空间
(继续在后面开辟空间去使用)
(2)原有空间之后没有足够大的空间
(将旧的空间的数据,拷贝一份到新的空间;返回新的空间的起始地址;释放掉旧的地址) -
使用:
#include<stdio.h>
int main()
{//申请一块空间,用来存放1—5的数字int* p = (int*)malloc(5 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//使用内存空间int i = 0;for (i = 0;i < 5;i++){p[i] = i + 1;}//增容int*p2=realloc(p, 10 * sizeof(int));if (p2 == NULL){perror("realloc");free(p);p = NULL;return 1;}p = p2;//继续使用p来维护新的空间for (i = 5;i < 10;i++){p[i] = i + 1;}free(p);p = NULL;return 0;
}
当然,realloc也可以用来开辟空间,如下:
int main()
{int* p = (int*)realloc(NULL, 40);//等价于malloc(40)//...return 0;
}
3.常见的动态内存的错误
(1).对NULL指针的解引用操作
产生原因:没有对malloc,realloc,calloc函数的返回值做判断。
int main()
{int*p=(int*)malloc(INT_MAX);int i=0;for(i=0;i<10;i++){p[i]=i+1;//这里可能因为p为空指针而出错//需要再循环前加上:/*if(p==NULL){perror("malloc");return 1;}*/}free(p);p=NULL;return 0;
}
(2).对动态开辟空间的越界访问
int main()
{int*p=(int*)malloc(10*sizeof(int));if(p==NULL){perror("malloc");return 1;}int i=0;for(i=0;i<=10;i++){p[i]=i+1;//当i等于10的时候越界访问}free(p);p=NULL;return 0;
}
(3).对非动态开辟内存使用free访问
int main()
{int arr[10]={1,2,3,4,5,6,7,8,9};//栈区上的数组int* p=arr;//使用free(p);//手动回收的空间也需要手动开辟,不能回收非动态开辟内存p=NULL;return 0;}//当程序退出main函数时会自动回收
这会导致程序未响应
(4).使用free释放一块动态内存的一部分
int main()
{int*p=(int*)malloc(10*sizeof(int));if(p==NULL){perror("malloc");return 1;}int i=0;for(i=0;i<5;i++){*p=i+1;p++;//这样写会出现问题,p不再指向gakongjian如下图//这样写是没问题的://*(p+i)=i+1;}free(p);//当free释放空间的时候,一定要给该空间的起始位置p=NULL;return 0;
}
(5).对同一块动态内存多次释放
int main()
{int*p=(int*)mallloc(10*sizeof(int));if(p=NULL){perror("malloc");return 1;}//使用free(p);//为避免错误,free释放后要及时将p置为空//即p=NULL;//...free(p);//二次释放了p=NULL;return 0;
}
这会导致程序未响应
(6).动态内存忘记释放(内存泄漏)
忘记手动使用free进行动态内存释放会导致内存泄漏。
应做到谁申请的空间谁释放;不使用空间及时释放;自己(函数1)不方便释放的空间,要告诉别人(函数2)释放
4.动态内存经典例题
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char* p)//正确方法:(char** p)
{p = (char*)malloc(100);//改正:*p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(str);//改正:GetMemory(&str);strcpy(str, "hello world");printf(str);/*释放:free(str);str=NULL;*/
}
int main()
{Test();return 0;
}//存在对NULL的解引用操作,程序崩溃
程序崩溃原因:
- 当str传递给GetMemory函数的时候,采用的是值传递,形参变量p只是str的一份拷贝。当把malloc申请的空间的起始地址存放在p中时不会修改str,str依然为NULL。因而当GetMemory函数返回后,再去调用strcpy函数需要将"hello world"拷贝到str指向的空间时,程序崩溃;
- 且malloc申请的空间没有手动去释放,存在内存泄露
//返回栈空间地址的问题
char* GetMemory(void)
{char p[] = "hello world";return p;//p是一个局部变量,是栈区上的数组,出了作用域这块空间就销毁
}
void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}
int main()
{Test();return 0;
}//打印出随机值
//正确改法:static char p[] = "hello world";
//加入static该数组就存在内存的静态区了,退出后不会销毁
//内存泄漏问题
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello world");printf(str);//释放:free(str);//str=NULL;
}
int main()
{Test();return 0;
}
//未使用free去释放malloc开辟的内存,内存泄露了
//野指针问题
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);//内存提前释放了,str称为野指针if (str != NULL){strcpy(str, "world");//这里非法访问内存printf(str);}
}
int main()
{Test();return 0;
}//程序崩溃,什么都打印不出来
//正确做法,在释放后把指针置为空
// 即str=NULL;
柔性数组
- C99中,结构中的最后一个元素允许是位置大小的数组,这就叫【柔性数组】成员。例如:
struct st
{int i;int a[0];//大小未知,即柔性数组成员//或者写成:int a[];
}
柔性数组的特点
-
结构中的柔性数组成员前面必须至少有一个其他成员
-
sizeof返回的这种结构大小不包括柔性数组的内存
-
包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小,如:
#include<stdio.h>
#include<stdlib.h>
struct S
{int n;int arr[];//柔性数组成员
};
int main()
{struct S* ps=(struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));if (ps == NULL){perror("malloc");return 1;}//使用空间ps->n = 100;int i = 0;for (i = 0;i < 10;i++){ps->arr[i] = i + 1;}
//使用柔性数组,用malloc创建后,可随时调整空间//调整空间struct S* tmp=realloc(ps,sizeof(struct S)+20*sizeof(int));if(tmp==NULL){perror("realloc");return 1;}ps=tmp;//释放空间free(ps);ps = NULL;return 0;
}
当然,我们也可以自己写一个类似柔性数组的程序,用结构体包含一个指针的做法,代码如下:
struct S
{int n;int* arr;
};
int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){perror("malloc");return 1;}ps->n = 100;int* ptr = (int*)malloc(10 * sizeof(int));if (ptr == NULL){perror("malloc2");return 1;}ps ->arr=ptr;int i = 0;for (i = 0;i < 10;i++){ps->arr[i] = i + 1;}//当然也可以调整空间ptr=realloc(ps->arr, 20 * sizeof(int));if (ptr == NULL){perror("realloc");return 1;}ps->arr = ptr;free(ps->arr);free(ps);ps = NULL;return 0;
}
对比一下,柔性数组只需用malloc申请一次空间就满足结构体内存的使用,而我们自己写的则需要两次malloc进行内存创建。我们要知道,malloc用得越多,内存碎片就越多,浪费的也就越多。这样比较下来,柔性数组的优点是不是更显著一点了。
使用柔性数组的优点有:
- 方便内存释放
- 有利于提升访问速度
本次内容到这里就结束了,谢谢观看!