动态内存分配可以说是C语言中的超级大BOSS,他能使用的地方有许多许多,而且与free搭配使用。很多初学者,经常会忘记这个的使用。
而且它也是数据结构中很关键的一环。在各种的链表,顺序表等等,我们都需要去使用它。所以这是一个非常重要的一节
为什么存在动态内存分配
我们以尽掌握的内存开辟方式
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
但是上述的开辟方式有两个特点:
1.空间的开辟大小是固定的。
2.数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但比如我们在动态顺序表中,我们先开辟了一部分,但是在分配过程中,我们所分配的内存不够了,此时我们就需要malloc函数先临时开辟一个空间,之后再去使用,当使用完成之后在free释放掉。
int main(){
int len = 0;
printf("请输入长度:");
scanf("%d",&len);
//malloc只是动态申请一块连续的内存空间
int* a = (int*)malloc(len * sizeof(int));
return 0;
}
malloc()函数的优点
1.申请内存空间的长度能够比较灵活
2.何时释放,完全由用户来掌控
malloc
void* malloc(size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
free
void free(void* ptr);
free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
如果频繁 malloc 但是忘记 free,此时就是内存泄漏
关于一些经典的动态分配
1.首先不能malloc()两次,或者free两次。动态分配两次也就是说你释放的时候只能释放一个,另一个并没有释放掉。即使你释放了两次。
2.free必须搭配malloc函数来使用。如果定义一个指针的地址,是不能释放掉的。这是错误的操作。
3.还有特殊规定,C++标准中规定了对 空指针进行 free/delete,都是合法的(无事发生)。所以当free一个内存之后,就把这个指针设为NULL。推荐这样做
int * p = (int*)malloc(sizeof(int));
free(p);
p = NULL;
//如果*p = 100;这就是一个未定义行为
calloc
void* calloc(size_t num,size_t size);
函数的功能是为num
个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0.
与函数malloc
的区别只在于calloc
会返回地址之前把申请的空间的每个字节初始化为全0.
realloc
void* realloc(void* ptr,size_t size);
当我们发现我们申请的内存空间过大或者过小,我此时就可以使用realloc
函数就可以对动态开辟内存大小的调整
ptr
是要调整的内存地址
size
调整之后新的大小
返回值为调整之后的内存起始位置
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间上
一些常见的动态内存错误
对NULL指针的解引用操作
void test(){
int *p = (int*)malloc(INT_MAX/4);
*p = 20;
free(p);
}
对动态开辟空间的越界访问
void test(){
int i = 0;
int *p = (int*)malloc(10*sizeof(int));
if(NULL == p){
exit(EXIT_FAILURE);//是个宏,返回1成功,返回0失败
}
for(i = 0;i <= 10;i++){
*(p+i) = i;//当i是10的时候越界访问,*(p+i)相当于p[i],i为10时,数组下标越界
}
free(p);
}
使用free释放一块动态开辟内存的一部分
void test(){
int *p = (int*)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
free只能释放开辟的所有空间,这种行为是未定义行为
经典笔试题
第一题
void GetMemory(char *p){
p = (char *)malloc(100);
}
void Test(void){
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
在Test函数中,*str是实参,当通过GetMemory调用之后的str是形参,当函数结束结束之后,就不存在了,str依旧是一个空指针,将字符拷贝到一个空指针中,这将发生段错误,也就是未定义行为。应该修改为
void GetMemory(char **p){
*p = (char *)malloc(100);
}
void Test(void){
char *str = NULL;
GetMemory(&str);
strcpy(str, "hello world");
printf("%s\n",str);
free(str);
}
第二题
char *GetMemory(void)
{
char p[] = "hello world";
return p;//返回的是数组的首元素地址
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
但是p[]数组仅仅是一个局部变量,函数结束,内存就释放了。应该修改为
const char *GetMemory(void)
{
const char* p = "hello world";
return p;
}
void Test(void)
{
const char *str = NULL;
str = GetMemory();
printf("%s\n",str);
}
虽然内存可以访问,但是不可以修改
第三题
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
首先没有释放内存,并且在我们的调用过程中最好进行一个if对str的判断
第四题
void Test(void){
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL){
strcpy(str, “world”);
printf(str);
}
}
释放完毕之后就不能在使用了,此时str就相当于一个野指针,虽然有结果打印出来,但是这个打印是非法的。
那么我们什么时候需要malloc内存?
当申请的内存空间比较大的时候,需要malloc
什么时候血药直接定义临时变量?
如果对于内存申请的性能要求较高的时候
柔性数组
只在C语言中存在
typedef struct Test{
int i;
int a[0];//a成员就是柔性数组成员
}Test;
typedef struct Test2{
int i;
int* a;
}Test;
int main(){
Test* t = (Test*)malloc(sizeof(int)+sizeof(int)*10);
t->i = 10;
for(int i = 0;i < 10;i++){
t->a[i] = i;
}
free(t);
Test2* t2 = (Test2*)malloc(sizeof(int)+sizeof(int)*100);
t2->i = 10;
t2->a = (int*)malloc(sizeof(int) * 10);
free(t2->a);
free(t2);
return 0;
}
柔性数组的特点:
结构中的柔性数组成员前面必须至少一个其他成员。
sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。