C语言——动态内存管理

时间:2022-10-28 22:54:54

一.为什么存在动态内存分配

//局部变量
int a = 2;//在栈区开辟4个字节
char arr[10] = {0};//在栈区开辟10个字节的连续的空间

上述的空间开辟方式有两个特点:

  1. 开辟空间大小是固定的
  2. 数组在声明的时候必须指定数组的长度,其所需要的内存在编译时分配

不过对于空间的开辟,有时我们需要想随便开辟多大空间的时候,就需要用到动态内存分配

C语言——动态内存管理


二.动态内存函数的介绍

头文件均为   <stdlib.h>

malloc(开辟动态内存空间)

C语言——动态内存管理

  • 开辟空间成功时,返回指向函数分配的内存块的指针。
  • 开辟空间失败时,则返回一个空指针(NULL)。因此malloc函数的返回值一定要做检查
  • 返回值的类型是void* ,具体情况下进行强制类型转换
  • malloc函数和free函数应一块使用

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
//想内存申请10个整形的空间
int* p = (int*) malloc(40);//void*强制类型转换为int*
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
else//开辟成功
{
int i = 0;
for(i = 0; i<10;i++)
{
*(p + i) = i;
printf("%d\n",*(p + i));
}
}
return 0;
}


free(释放动态内存空间)

   

C语言——动态内存管理

  • 参数(ptr)为 指向以前使用 或 分配的内存块的指针
  • 使用时,得有free函数应一块使用
  • 如果参数 ptr 指向的空间不是动态开辟的,则free函数的行为是未定义的
  • 如果参数ptr为NULL指针,则free函数什么都不会做

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
//想内存申请10个整形的空间
int* p = (int*) malloc(10*sizeof(int));//void*强制类型转换为int*
//或int* p = (int*)calloc(40);
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
else//开辟成功
{
int i = 0;
for(i = 0; i<10;i++)
{
*(p + i) = i;
printf("%d\n",*(p + i));
}
}

free(p);
p = NULL;
return 0;
}

注意:

//当动态申请的空间不再使用的时候,就应该还给操作系统

//虽然程序结束后由于生命周期而自动会还给操作系统,但当我们后续还有,这回导致这白白浪费

//虽然free(p)释放了p的空间后,但p仍能找到这个空间,这是具有潜在危险的,所以我们可以直接将其置空


calloc(开辟动态内存空间)

C语言——动态内存管理

  • 返回值是指向开辟的空间的指针
  • 开辟空间失败时,则返回一个空指针(NULL)
  • calloc与malloc参数用法不同;calloc会把空间的每个字节初始化为0,而malloc不会初始化
  • 使用时,得有free函数应一块使用

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
//想内存申请10个整形的空间
int* p = (int*)calloc(10,sizeof(int));//void*强制类型转换为int*
//或int* p = (int*)calloc(10,4);
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
else//开辟成功
{
int i = 0;
for(i = 0; i<10;i++)
{
printf("%d\n",*(p + i));
}
}

free(p);
p = NULL;
return 0;
}
//0 0 0 0 0 0 0 0 0 0


realloc(调整动态内存的大小)

C语言——动态内存管理

realloc函数能让动态内存管理更加灵活,在发现过去申请的内存空间过小或过大,可以使用realloc函数进行大小的调整


  • 如果p指向的空间后有足够的内存空间可以追加,则直接追加,返回p
  • 如果p指向的空间后没有足够的内存空间可以追加,则realloc函数会重找一个能满足需求的内存空间,并将原来内存中的数据拷贝过来,谁放掉旧的空间,最后返回新的内存空间
  • realloc函数调整失败后会返回一个空指针,不能拿p直接接收realloc,得用一个新的变量来接收返回值
  • 使用时,得有free函数应一块使用

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
//想内存申请10个整形的空间
int* p = (int*)malloc(20);
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
else//开辟成功
{
int i = 0;
for(i = 0; i<5;i++)
{
*(p + i) = i;
printf("%d ",*(p + i));
}
}

int* p2 = (int*)realloc(p,40);
int i = 0;
for(i = 5;i < 10;i++);
{
printf("%d ",*(p2 + i));
}
//free(p);
//p = NULL;

return 0;
}


注意:

//当realloc第一个参数为NULL时,可以相当于malloc
int* p = (int*)realloc(NULL, 40);


三.常见的动态内存错误

对空指针的解引用操作

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* p = (int*) malloc(40);

//未对失败的情况下判定
//万一malloc失败了,p被赋值为NULL
//而下面的操作,都成了非法操作

int i = 0;
for(i = 0; i<10;i++)
{
*(p + i) = i;
}

free(p);
p = NULL;

return 0;
}

对动态内存开辟空间的越界访问

开辟动态内存空间后,后面进行操作时却超出这个空间的大小

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* p = (int*) malloc(40);//void*强制类型转换为int*
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
else//开辟成功
{
int i = 0;
for(i = 0; i<20;i++)// 进行越界访问了
{
*(p + i) = i;
}
}
return 0;
}

对非动态开辟内存使用free函数释放

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int a = 10;//a在栈区
int *p = &a;
*p = 20;
free(p);//free作用在堆区,强行乱指会造成程序崩溃

return 0;
}

使用free释放一块动态开辟内存的一部分

由于使p的地址发生改变时,释放的不再是原来的初始地址

#include <stdio.h>
#include <stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* p = (int*)malloc(10*sizeof(int));
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}

int i = 0;
for (i = 0; i < 5; i++)
{
*p++ = i;
}
free(p);
p = NULL;

return 0;
}

对一块动态内存进行多次释放


#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* p = (int*) malloc(40);//void*强制类型转换为int*
if(p == NULL)//开辟失败
{
printf("%s\n",strerror(errno));//打印错误原因
}
else//开辟成功
{
int i = 0;
for(i = 0; i<10;i++)// 进行越界访问了
{
*(p + i) = i;
}
}

free(p);

free(p);
p = NULL;

return 0;
}
  • 可以在一个free后紧跟着将其置空,后面再出现时不会有影响
free(p);
p = NULL;
free(p);

动态开辟内存忘记释放(内存泄露)

#include <stdio.h>
#include <stdlib.h>
void test()
{
int* p = (int*)malloc(100);
if(p != NULL)
{
*p = 20;
}
}
int main()
{
test();
while(1);

}

忘记释放不再使用的动态开辟的空间会造成内存泄露

————动态开辟的空间一定要进行正确的释放

注意:

动态开辟的空间有2种回收方式:主动free   ,   程序结束

四.柔性数组

1.定义

在 C99 中,结构体中的最后一个元素允许是未知大小的数组,即柔性数组

struct a{
int b;
int arr[];//大小是未知的
};

//或

struct a{
int b;
int arr[0];//大小是未知的
};

2.特点

  •  结构体中的 柔性数组成员 的 前面 必须至少有一个其它成员
  • sizeof 计算这种结构体的大小 不包含柔性数组成员的
  • 包含柔性数组成员的结构体用 malloc 函数进行内存分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
#include<stdio.h>
#include<stdlib.h>
struct A{
int b;
int arr[];//大小是未知的
};
int main()
{
//期待arr的大小是10个整形
//结构体的初始化
struct A* ps = (struct A*)malloc(sizeof(struct A ) + 10*sizeof(int));//前4个字节是给b的,后40个字节是给arr的
//由于后面的是通过malloc开辟的动态内存,大小是可以调整的
printf("%d",sizeof(ps));
free(ps);
ps = NULL;
return 0;
}

//4