关于我、重生到500年前依附C语言改变天下科技vlog.21——动态内存管理 ...

打印 上一主题 下一主题

主题 970|帖子 970|积分 2910

通常我们开辟空间都是固定的,然后再在这上面操作,但是万一我们想要修改可利用的空间呢,归去反复的修改有些麻烦。很多数据布局的大小在程序运行时才能确定,好比有个学生信息录入,会有不断学生将信息录入,无法事先知道应该用多大的空间来存放,那么动态内存的开辟就很有用了,可以在编译过程中修改可利用的空间大小
1. 内存的开辟与释放

已往我们开辟空间的方式有:
  1. int val = 20;//在栈空间上开辟四个字节
  2. char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
复制代码
但是这种方式存在缺点:
   空间开辟大小是固定的数组在申明的时间,必须指定命组的长度,数组空间一旦确定了大小不能调整
  1.1 malloc

malloc 是一种常用的开辟空间的函数,它实用于各种范例的内存开辟。这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。声明在 stdlib.h 头文件中

   传送门:malloc-C++参考
  参数:size-表示需要分配的字节数
返回值:返回一个指向所分配内存块起始地址的指针
值得注意的是
• 假如开辟乐成,则返回⼀个指向开辟好空间的指针
• 假如开辟失败,则返回⼀个 NULL 指针,因此 malloc 的返回值⼀定要做检查
• 返回值的范例是 void* ,所以 malloc 函数并不知道开辟空间的范例,具体在利用的时间利用者自己来决定
• 假如参数 size 为0,malloc 的行为是尺度是未定义的,取决于编译器
eg:开辟100个字节
  1. int *p = (int *) malloc(100)
  2. if(p != NULL)
  3. {
  4.          perror("malloc");
  5. }
  6. else
  7. {
  8.          return 1;
  9. }
复制代码
一般开辟了空间,就要检查该空间是否开辟乐成
   malloc 或许还可以开辟 INT_MAX 的内存大小,但是在实际运行中,当你试图利用malloc(INT_MAX) 时,几乎肯定会导致内存分配失败,这只是一个理论上存在的值,malloc 函数会返回 NULL ,这是由于系统没有足够的连续空闲内存来满意如许大的请求
  1.2 free

malloc 开辟的内存是动态的,也就是说在程序竣事时假如不释放的话,会一直占用空间,造成内存走漏
1.2.1 为什么要释放内存?

起首我们要知道什么是内存走漏
   内存走漏是指程序动态分配的内存空间在利用完毕后没有被释放,导致这部门内存一直被占用。当程序中存在内存走漏时,随着程序的运行,被走漏的内存会不断累积
  所以长时间运行的程序,内存走漏大概会导致系统内存逐渐被耗尽。当系统内存不足时,程序大概会出现性能下降、运行迟钝乃至崩溃的情况
1.2.2 free的利用

释放和回收动态内存的函数为 free ,声明在 stdlib.h 头文件中

   传送门:free-C++参考
  参数:指向先前利用或分配的内存块的指针
值得注意的是
• 假如参数 ptr 指向的空间不是动态开辟的,那 free 函数的行为是未定义的
• 假如参数 ptr 是 NULL 指针,则函数什么事都不做
• free 的内存释放可以明白为这块内存的利用权被取消掉了,而内存的回收销毁是栈实现的
eg
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5.          int num = 0;
  6.          scanf("%d", &num);
  7.          int arr[num] = {0};
  8.          int* ptr = NULL;
  9.          ptr = (int*)malloc(num*sizeof(int));
  10.          if(NULL != ptr)//判断ptr指针是否为空
  11.          {
  12.                  int i = 0;
  13.                  for(i=0; i<num; i++)
  14.                  {
  15.                          *(ptr+i) = 0;
  16.                  }
  17.          }
  18.          free(ptr);//释放ptr所指向的动态内存
  19.          ptr = NULL;//是否有必要?
  20.          return 0;
  21. }
复制代码
ptr = NULL 是有须要的,此时的 ptr 仍然指向该内存,但是这个内存已经被释放了,假如后续代码中错误地访问*p(好比试图修改或读取这个已经释放的内存空间中的值),就会导致程序出现未定义行为,大概会出现程序崩溃、数据错误等情况
2.内存的初始化和修改

   malloc 提供的功能是开辟内存空间,万一我们想要增长利用的空间,可以再次利用 malloc开辟空间,但这又得创建一个变量存储,释放空间就要多次释放,这不免容易忘记,而且多次利用 malloc 开辟的空间通常是不连续的,那里有空间他就在那里开辟,那么就需要 realloc 函数提供修改动态空间的功能了
  2.1 calloc

calloc 函数也用来动态内存分配,但是它可以初始化动态内存的内容,声明在 stdlib.h 头文件中

   传送门:calloc-C++参考
  参数:num-元素个数,size-元素大小
返回值:指向函数分配的内存块的指针
值得注意的是
• 函数的功能是为 num 个大小为 size 的元素开辟一块空间,而且把空间的每个字节初始化为 0
• 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0
eg
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5.          int *p = (int*)calloc(10, sizeof(int));
  6.          if(NULL != p)
  7.          {
  8.                  int i = 0;
  9.                  for(i=0; i<10; i++)
  10.                  {
  11.                          printf("%d ", *(p+i));
  12.                  }
  13.      }
  14.          free(p);
  15.          p = NULL;
  16.          return 0;
  17. }
复制代码
输出的内容为 10 个 0,证实 calloc 确实初始化开辟的空间为 0
2.2 realloc

realloc函数的出现让动态内存管理更加机动,可以修改原先开辟的动态内存,声明在 stdlib.h 头文件中

   传送门:realloc-C++参考
  参数:ptr-指向先前利用或分配的内存块的指针,size-元素大小
返回值:指向重新分配的内存块的指针
值得注意的是
• ptr 是要调整的内存地址
• size 调整之后新大小(包罗原来的大小)
• 返回值为调整之后的内存起始位置
• 这个函数调整原内存空间大小的根本上,还会将原来内存中的数据移动到新的空间
• realloc 在调整内存空间的是存在两种情况
情况1:原有空间之后有足够大的空间

直接在原有空间后面加上新的空间
情况2:原有空间之后没有足够大的空间

假如后续空间不敷, realloc 函数直接在内存的堆区找一块新的满意大小的空间,将旧的数据拷贝到新的空间,原来的空间则主动释放,返回新的地址
eg
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5.         int* ptr = (int*)malloc(100);
  6.         if (ptr != NULL)
  7.         {
  8.                 perror("malloc");
  9.         }
  10.         else
  11.         {
  12.                 return 1;
  13.         }
  14.         //扩展容量
  15.         //代码1 - 直接将realloc的返回值放到ptr中
  16.         ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
  17.         //代码2 - 先将realloc函数的返回值放在p中,不为NULL,在放ptr中
  18.         int* p = NULL;
  19.         p = realloc(ptr, 1000);
  20.         if (p != NULL)
  21.         {
  22.                 ptr = p;
  23.         }
  24.         //业务处理
  25.         free(ptr);
  26.         return 0;
  27. }
复制代码
代码1
   假如内存重新分配失败(比方,系统没有足够的连续内存来满意 1000 个 int 范例数据的内存需求),realloc 函数会返回 NULL,但这里的问题是,当它返回 NULL 时,原始的 ptr 所指向的内存块已经被释放(由于 realloc 在尝试重新分配失败时,会释放掉原始的内存块以避免内存走漏),这就导致 ptr 变为 NULL,而且之前通过 ptr 可访问的原始数据也丢失了,后续若再尝试利用 ptr 就会导致程序出错
  代码2
   

  • 先将 realloc 函数的返回值赋给 p ,然后进行判定的做法更为稳妥,同样执行 p = realloc(ptr, 1000),当重新分配乐成时,通过判定 p!= NULL 能确认重新分配乐成,然后再将 p 的值赋给 ptr,使得 ptr 精确指向新的内存块
  • 假如重新分配失败,realloc 会返回 NULL,此时 p 为 NULL,由于没有直接将 NULL 赋给 ptr,所以 ptr 仍然指向原来的内存块(条件是原来的内存块还未被 realloc 释放,在这种情况下,原来的内存块未被释放是由于重新分配失败后没有进行释放原始内存块的操作),如许就可以避免丢失原始数据以及出现空指针错误
  3.柔性数组

3.1 什么是柔性数组?上风是什么?

   柔性数组是 C99 尺度中引入的一个特性,它是在一个布局体的最后一个成员位置定义的数组,而且这个数组的大小是可以机动变化的,大小是未知的
  1. struct my_struct
  2. {
  3.     int num;
  4.     int data[];
  5. };
复制代码
在这个布局体my_struct中,data就是一个柔性数组
值得注意的是
• 布局中的柔性数构成员前面必须至少一个其他成员
• sizeof 返回的这种布局大小不包括柔性数组的内存
• 包罗柔性数构成员的布局用 malloc () 函数进行内存的动态分配,而且分配的内存应该大于布局的大小,以适应柔性数组的预期大小
3.2 柔性数组的利用

  1. typedef struct st_type
  2. {
  3.          int i;
  4.          int a[0];//柔性数组成员
  5. }type_a;
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. int main()
  9. {
  10.         int i = 0;
  11.          type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
  12.          p->i = 100;
  13.          for(i=0; i<100; i++)
  14.          {
  15.                  p->a[i] = i;
  16.          }
  17.          free(p);
  18.          return 0;
  19. }
复制代码
依次将 i 的值赋值给柔性数组,柔性数组能被赋多少,那他的空间一般就为多少,如许柔性数构成员 a,相当于得到了 100 个整型元素的连续空间
但是不提倡在布局体里进行内存的二次分配,用户调用 free 可以释放布局体,但是用户并不知道这个布局体内的成员也需要 free,所以你不能指望用户来发现这个事
4. C/C++内存分配

以一段代码做例子:
  1. int num = 1;
  2. static int ret = 1;
  3. void test()
  4. {
  5. int nums[10] = {1,2,3,4};
  6. char ch[5] = "abcde";
  7. char *sh = "zxcv"
  8. int *ptr1 = (int*)malloc(sizeof(int)*4);
  9. int *ptr2 = (int*)calloc(4,sizeof(int));
  10. int *ptr3 = (int*)realloc(ptr2,sizeof(int)*4);
  11. }
复制代码
一般程序中内存地区分别为:

  • 内核空间(用户代码不能读写)
  • 栈(向下增长)
    在执行函数时,函数内局部变量的存储单位都可以在栈上创建,函数执行竣事时这些存储单位主动被释放,栈内存分配运算内置于处理处罚器的指令会合,效率很高,但是分配的内存容量有限,栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等
  1. int nums[10] ,char ch[5] ,char *sh
  2. int *ptr1 ,int *ptr2 ,int *ptr3
复制代码

  • 内存映射段(文件映射、动态库、匿名映射)
  • 堆(向上增长)
    ⼀般由程序员分配释放,若程序员不释放,程序竣事时大概由OS回收,分配方
    式雷同于链表,堆区主要存放动态开辟的内存空间等
  1. (int*)malloc(sizeof(int)*4);
  2. (int*)calloc(4,sizeof(int));
  3. (int*)realloc(ptr2,sizeof(int)*4);
复制代码

  • 数据段(全局数据、静态数据)
(static)存放全局变量、静态数据,程序竣事后由系统释放
  1. int num , static int ret
复制代码

  • 代码段(可执行代码/只读常量)
存放函数体(类成员函数和全局函数)的二进制代码
希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我进步的动力!



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

没腿的鸟

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表