当前位置:首页 » 《关注互联网》 » 正文

关于我、重生到500年前凭借C语言改变世界科技vlog.21——动态内存管理

25 人参与  2024年11月27日 18:02  分类 : 《关注互联网》  评论

点击全文阅读


文章目录

1. 内存的开辟与释放1.1 malloc1.2 free1.2.1 为什么要释放内存?1.2.2 free的使用 2.内存的初始化和修改2.1 calloc2.2 realloc 3.柔性数组3.1 什么是柔性数组?优势是什么?3.2 柔性数组的使用 4. C/C++内存分配希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力!
通常我们开辟空间都是固定的,然后再在这上面操作,但是万一我们想要修改可使用的空间呢,回去反复的修改有些麻烦。许多数据结构的大小在程序运行时才能确定,比如有个学生信息录入,会有不断学生将信息录入,无法事先知道应该用多大的空间来存放,那么动态内存的开辟就很有用了,可以在编译过程中修改可使用的空间大小

1. 内存的开辟与释放

过去我们开辟空间的方式有:

int val = 20;//在栈空间上开辟四个字节char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

但是这种方式存在缺点:

空间开辟大小是固定的数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小不能调整

1.1 malloc

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

在这里插入图片描述

传送门:malloc-C++参考

参数:size-表示需要分配的字节数

返回值:返回一个指向所分配内存块起始地址的指针

值得注意的是

• 如果开辟成功,则返回⼀个指向开辟好空间的指针

• 如果开辟失败,则返回⼀个 NULL 指针,因此 malloc 的返回值⼀定要做检查

• 返回值的类型是 void* ,所以 malloc 函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定

• 如果参数 size 为0,malloc 的行为是标准是未定义的,取决于编译器

eg:开辟100个字节

int *p = (int *) malloc(100)if(p != NULL) { perror("malloc"); } else { return 1; }

一般开辟了空间,就要检查该空间是否开辟成功

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

#include <stdio.h>#include <stdlib.h>int main(){ int num = 0; scanf("%d", &num); int arr[num] = {0}; int* ptr = NULL; ptr = (int*)malloc(num*sizeof(int)); if(NULL != ptr)//判断ptr指针是否为空 { int i = 0; for(i=0; i<num; i++) { *(ptr+i) = 0; } } free(ptr);//释放ptr所指向的动态内存 ptr = NULL;//是否有必要? return 0;}

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

#include <stdio.h>#include <stdlib.h>int main(){ int *p = (int*)calloc(10, sizeof(int)); if(NULL != p) { int i = 0; for(i=0; i<10; i++) { printf("%d ", *(p+i)); }     } free(p); p = NULL; return 0;}

输出的内容为 10 个 0,证明 calloc 确实初始化开辟的空间为 0

2.2 realloc

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

在这里插入图片描述

传送门:realloc-C++参考

参数:ptr-指向先前使用或分配的内存块的指针,size-元素大小

返回值:指向重新分配的内存块的指针

值得注意的是

• ptr 是要调整的内存地址

• size 调整之后新大小(包含原来的大小)

• 返回值为调整之后的内存起始位置

• 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

• realloc 在调整内存空间的是存在两种情况

情况1:原有空间之后有足够大的空间

在这里插入图片描述
直接在原有空间后面加上新的空间

情况2:原有空间之后没有足够大的空间
在这里插入图片描述
如果后续空间不够, realloc 函数直接在内存的堆区找一块新的满足大小的空间,将旧的数据拷贝到新的空间,原来的空间则自动释放,返回新的地址

eg

#include <stdio.h>#include <stdlib.h>int main(){int* ptr = (int*)malloc(100);if (ptr != NULL){perror("malloc");}else{return 1;}//扩展容量//代码1 - 直接将realloc的返回值放到ptr中ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)//代码2 - 先将realloc函数的返回值放在p中,不为NULL,在放ptr中int* p = NULL;p = realloc(ptr, 1000);if (p != NULL){ptr = p;}//业务处理free(ptr);return 0;}

代码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 标准中引入的一个特性,它是在一个结构体的最后一个成员位置定义的数组,并且这个数组的大小是可以灵活变化的,大小是未知的

struct my_struct {    int num;    int data[];};

在这个结构体my_struct中,data就是一个柔性数组

值得注意的是

• 结构中的柔性数组成员前面必须至少一个其他成员

• sizeof 返回的这种结构大小不包括柔性数组的内存

• 包含柔性数组成员的结构用 malloc () 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

3.2 柔性数组的使用

typedef struct st_type{ int i; int a[0];//柔性数组成员}type_a;#include <stdio.h>#include <stdlib.h>int main(){int i = 0; type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int)); p->i = 100; for(i=0; i<100; i++) { p->a[i] = i; } free(p); return 0;}

依次将 i 的值赋值给柔性数组,柔性数组能被赋多少,那他的空间一般就为多少,这样柔性数组成员 a,相当于获得了 100 个整型元素的连续空间

但是不提倡在结构体里进行内存的二次分配,用户调用 free 可以释放结构体,但是用户并不知道这个结构体内的成员也需要 free,所以你不能指望用户来发现这个事

4. C/C++内存分配

以一段代码做例子:

int num = 1;static int ret = 1;void test(){int nums[10] = {1,2,3,4};char ch[5] = "abcde";char *sh = "zxcv"int *ptr1 = (int*)malloc(sizeof(int)*4);int *ptr2 = (int*)calloc(4,sizeof(int));int *ptr3 = (int*)realloc(ptr2,sizeof(int)*4);}

一般程序中内存区域划分为:

内核空间(用户代码不能读写)

栈(向下增长)
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放,栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限,栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等

int nums[10] ,char ch[5] ,char *sh int *ptr1 ,int *ptr2 ,int *ptr3 

内存映射段(文件映射、动态库、匿名映射)

堆(向上增长)
⼀般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方
式类似于链表,堆区主要存放动态开辟的内存空间等

(int*)malloc(sizeof(int)*4);(int*)calloc(4,sizeof(int));(int*)realloc(ptr2,sizeof(int)*4);
数据段(全局数据、静态数据)

(static)存放全局变量、静态数据,程序结束后由系统释放

int num , static int ret
代码段(可执行代码/只读常量)

存放函数体(类成员函数和全局函数)的二进制代码

希望读者们多多三连支持

小编会继续更新

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

请添加图片描述


点击全文阅读


本文链接:http://zhangshiyu.com/post/192991.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1