【C语言】动态内存管理

目录

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

二.动态内存函数的介绍

malloc(开辟动态内存空间)

​free(释放动态内存空间)

calloc(开辟动态内存空间)

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

三.常见的动态内存错误

对空指针的解引用操作

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

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

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

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

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

四.柔性数组

1.定义

2.特点 


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

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

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

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

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

二.动态内存函数的介绍

头文件均为   <stdlib.h>

malloc(开辟动态内存空间)

  • 开辟空间成功时,返回指向函数分配的内存块的指针。
  • 开辟空间失败时,则返回一个空指针(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(释放动态内存空间)

   

 

  • 参数(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(开辟动态内存空间)

  • 返回值是指向开辟的空间的指针
  • 开辟空间失败时,则返回一个空指针(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(调整动态内存的大小)

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

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值