C语言进阶第九课 --------动态内存管理

作者前言

🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂
​🎂 作者介绍: 🎂🎂
🎂 🎉🎉🎉🎉🎉🎉🎉 🎂
🎂作者id:老秦包你会, 🎂
简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂
喜欢学习C语言和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂
🎂个人主页::小小页面🎂
🎂gitee页面:秦大大🎂
🎂🎂🎂🎂🎂🎂🎂🎂
🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂


动态内存

  • **作者前言**
  • 动态内存的意义
  • 动态内存函数的介绍
    • malloc
    • free
    • calloc
    • realloc
  • 常见的动态内存错误
    • 对NULL指针的解引用操作
    • 对动态内存的越界访问
    • 对非动态开辟的内存进行free释放
    • 使用free释放动态开辟的内存的一部分
    • 对一块空间多次释放
    • 动态开辟内存忘记释放(内存泄漏)
  • 经典笔试题
    • C/C++中程序内存区域分布
  • 柔性数组
    • 柔性数组的特点
    • 使用柔性数组的好处
  • 总结

动态内存的意义

在我们空间的开辟有定义变量

#include<stdio.h>
int main()
{int a = 0;char arr[] = "sdsd";printf("%p\n", &a);printf("%p\n", arr);return 0;
}

甚至还有局部变量都会向内存申请空间,但是这些空间不擦长久,或者很难随意操作这些空间进行销毁,或者不能随意扩大空间
但是上述的开辟空间的方式有两个特点:

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

动态内存函数的介绍

malloc

在这里插入图片描述
这个函数的动态内存开辟函数,
size_t是一个无符号整数类型,表示需要分配的内存大小(以字节为单位)。malloc函数会在堆上分配一块指定大小的内存,并返回指向该内存块的指针。如果分配失败,则返回NULL。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = (int*)malloc(sizeof(int) * INT_MAX);if (arr == NULL){perror("malloc:");return 1;}return 0;
}

可能有一些人会利用malloc申请0个字节,这种做法本身没有错误,但是这个就是脱裤子放屁多此一举
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
在这里插入图片描述

free

有心细的小可爱就会发现,当我们程序运行结束就会释放出开辟的动态内存(被动),如果我们要自己释放就要用到free函数
在这里插入图片描述
free是C语言中用于释放动态分配内存的函数。当我们使用malloc、calloc等函数在程序运行期间动态申请内存时,需要在使用完毕后使用free函数将其释放回系统,以便其他程序或进程可以使用。

其中,ptr是指向需要释放的内存块的指针。使用free函数可以将之前通过malloc、calloc等函数动态分配的内存块释放回系统,以避免内存泄漏和浪费。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = (int*)malloc(sizeof(int) * 10);if (arr == NULL){perror("malloc");return 1;}int i = 0;for (i = 0; i < 10; i++){arr[i] = i;printf("%d ", arr[i]);}free(arr);arr = NULL;return 0;
}

需要注意的是,在使用free函数释放内存块时需要注意以下几点:

只能释放之前通过malloc、calloc等函数动态分配的内存块,不能释放栈上的局部变量和全局变量等静态分配的内存;
不能重复释放同一块内存,否则会导致程序崩溃;
释放内存后,应该将指针设置为NULL,以避免出现野指针问题。

calloc

在这里插入图片描述
calloc是C语言中用于动态分配内存并进行初始化的函数。与malloc函数相似,calloc也用于在程序运行时动态分配内存,但与malloc不同的是,calloc会将分配的内存块初始化为零,而malloc不会
其中,num表示需要分配的元素个数,size表示每个元素的大小。calloc函数会分配num个大小为size的连续内存块,并将其初始化为零。如果分配成功,则返回指向分配内存块的指针;如果分配失败,则返回NULL。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = (int*)calloc(10, sizeof(int));if (arr == NULL){perror("calloc");return 1;}int i = 0;for (i = 0; i < 10; i++){arr[i] = i;}free(arr);arr = NULL;return 0;
}

realloc

realloc函数的出现让动态内存管理更加灵活,前面的malloc和calloc申请的空间无法避免申请空间过大或者过小,realloc函数就可以做到动态开辟内存的大小进行调整

realloc是C语言中用于重新分配内存的函数。在程序运行期间,我们有时需要重新分配内存块的大小,可以使用realloc函数来实现。realloc函数可以将之前分配的内存块的大小调整为新的大小,并返回指向新内存块的指针。
在这里插入图片描述
ptr是指向之前分配的内存块的指针,size是需要重新分配的内存块的大小。
如果重新分配成功,则返回指向新内存块的指针;如果分配失败,则返回NULL。需要注意的是,如果新的大小小于原来的大小,则realloc函数会截断原来的内存块;如果新的大小大于原来的大小,则realloc函数会在原来的内存块后面分配新的内存块。

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = (int*)malloc(sizeof(int) * 10);int* arr1 = (int*)calloc(20, sizeof(int));if (arr == NULL){perror("malloc");}if (arr1 == NULL){perror("calloc");}//调整空间int *arr3 = (int*)realloc(arr, 2000 * sizeof(int));if (arr3 != NULL){arr = arr3;}free(arr);free(arr1);arr = NULL;arr1 = NULL;arr3 = NULL;return 0;
}

realloc函数开辟的空间情况分两种
第一种:
在这里插入图片描述
之前malloc开辟空间的后面刚好满足扩大后的空间大小就会在原来的地址处重新扩大,这种情况返回的新地址就会和旧地址的值是一样的
在这里插入图片描述

第二种:

之前malloc开辟的空间的后面不能满足扩大后的空间大小,就会放弃在原来的空间扩大,反而会重新找一块能满足条件的内存进行开辟,一次性开辟好,会将旧的空间的数据就会拷贝到新的空间,旧的空间就会销毁
在这里插入图片描述

常见的动态内存错误

对NULL指针的解引用操作

这种情况的出现是因为没有对返回值进行判断造成的

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = malloc(sizeof(int) * INT_MAX);int i = 0;for (i = 0; i < INT_MAX; i++){arr[i] = 1;}return 0;
}

如上面代码一样,没有判断是否创建成功,直接解引用操作就会报错
在这里插入图片描述

对动态内存的越界访问

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

这种情况就和我们创建数组,越界访问是一样的

对非动态开辟的内存进行free释放

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int a = 0;int* p = &a;free(p);p = NULL;return 0;
}

这种情况就是懵了

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

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = calloc(10, sizeof(int));int i = 0; for (i = 0; i < 5; i++){*arr = i;arr++;}free(arr);arr = NULL;return 0;
}

在这里插入图片描述

对一块空间多次释放

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{int* arr = (int*)malloc(sizeof(int) * 10);if (arr == NULL){perror("malloc");return 1;}free(arr);free(arr);return 0;
}

解决这个错误我们可以养成一个习惯,只要释放过的空间,对应的变量全部赋值为NULL,因为free释放NULL不会有啥错误

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

include <stdio.h>
#include <stdlib.h>
#include <limits.h>
void Other()
{int* arr = (int*)malloc(sizeof(int) * 10);
}
int main()
{Other();while (1){;}return 0;
}

这样很容易造成空间不足,我们要记住谁申请,谁释放,如果没有释放交接任务也要告诉别人释放

经典笔试题

第一题

void GetMemory(char *p)
{p = (char *)malloc(100);
}
void Test(void)
{char *str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}

上面这些代码主要的错误就是当我们调用Test函数时,也会调用到 GetMemory函数,而传参传递的时变量str的值,而不是地址,最后str的值还是NULL,还造成了内存泄漏对NULL解引用就会报错,如果把 GetMemory函数更改为 GetMemory(char* *p ),然后传递str的地址
更改后:

void GetMemory(char* *p)
{*p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(&str);strcpy(str, "hello world");printf(str);free(str);str = NULL;
}
int main()
{Test();return 0;
}

还要注意的printf函数的使用没有错,是可以这样写的,这个写法只针对字符串
第二题

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
char* GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char* str = NULL;str = GetMemory();printf(str);
}
int main()
{Test();return 0;
}

这里的错误是GetMemory函数在调用结束后,p的空间销毁掉了,返回的值也就成了野指针
第三题

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include<string.h>
void GetMemory(char** p, int num)
{*p = (char*)malloc(num);
}
void Test(void)
{char* str = NULL;GetMemory(&str, 100);strcpy(str, "hello");printf(str);
}
int main()
{Test();return 0;
}

这道题主要是少了free释放,内存泄漏了
第四题

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include<string.h>
void Test(void)
{char* str = (char*)malloc(100);strcpy(str, "hello");free(str);if (str != NULL){strcpy(str, "world");printf(str);}
}
int main()
{Test();return 0;
}

这里的错误就是野指针,在我们释放了动态开辟的内存,str还是指向那块地址,这样就会非法访问内存,这里我们要注意释放后一定要赋值NULL

C/C++中程序内存区域分布

在这里插入图片描述
其中代码段是用来存储代码翻译成的二进制指令只读常量(字符串常量),里面的数据不可更改
在这里插入图片描述
这个图就可以很明了的知道动态内存开辟的空间是在堆区,局部变量和形式参数的空间是在栈区m全局变量和静态变量的空间是在数据段(静态区)
还要注意的是函数的空间也是在栈上创建的,函数栈帧的创建和销毁前面我已经写过,可以去看看
C/C++程序内存分配的几个区域:

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结
    束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是
    分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返
    回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分
    配方式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

柔性数组

这个概念是在C99引入的,之前是没有的
形成柔性数组的条件:

  1. 要在结构体中
  2. 是结构体的最后一个成员,并且这个数组是一个未知大小的数组(变长数组)

写法:

struct S{int a;int arr[];//柔性数组}a1;struct S1{int a;int arr[0];//柔性数组}a2;

代码中 的两个结构体属于两种写法,

柔性数组的特点

  1. 结构中的柔性数组成员前面必须至少一个其他成员。
  2. sizeof 返回的这种结构大小不包括柔性数组的内存。
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include<string.h>
struct S1
{int a;int arr[0];
};
int main()
{printf("%d", sizeof(struct S1);return 0;
}

在这里插入图片描述
可以看到sizeof返回的大小不包含柔性数组,假设结构体只有一个成员,而这个成员是柔性数组就会发现这个结构体的大小是0


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

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include<string.h>
struct S1
{int a;int arr[0];
};
int main()
{struct S1* p = (struct S1*)malloc(sizeof(int) + 20);if (p == NULL){perror("malloc");return;}p->a = 10;int i = 0;for (i = 0; i < 4; i++){p->arr[i] = i;}free(p);p = NULL;return 0;
}

其实我们可以不使用柔性数组,我们也可以模拟出来

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include<string.h>
struct S1
{int a;int arr[0];
};
struct S2
{int a;int* arr;
};
int main()
{/*struct S1* p = (struct S1*)malloc(sizeof(int) + 20);if (p == NULL){perror("malloc");return;}p->a = 10;int i = 0;for (i = 0; i < 4; i++){p->arr[i] = i;}free(p);p = NULL;*/struct S2 a2;a2.a = 10;a2.arr = (int*)malloc(sizeof(int) * 4);if (a2.arr == NULL){perror("malloc");return;}printf("%d", sizeof(struct S2));free(a2.arr);a2.arr = NULL;return 0;
}

使用这种方法虽然可以代替柔性数组,但是使用free的次数会增多,而使用柔性数组只需使用一次free

当我们使用malloc开辟的数次越多,产生的内存碎片就会越多,内存的利用率就会下降

使用柔性数组的好处

第一个好处是:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
第二个好处是:这样有利于访问速度.
连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)

总结

在这里介绍了动态内存的开辟、常见错误柔性数组,有不懂的小可爱可以私聊

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/117985.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

k8s-----19、Helm

Helm 1、引入2、概述2.1 重点2.2 V3版本的Helm2.2.1 与之前版本的不同之处2.2.2 V3版本的运行流程 3、安装和配置仓库、一些附带操作3.1 安装3.2 配置仓库3.3 常用命令3.4 添加helm的自动补齐 4、快速部署应用(weave应用)5、 自行创建Chart5.1 Chart目录内容解析5.2 简单安装部…

codeforces (C++ Haunted House)

题目&#xff1a; 翻译&#xff1a; 思路&#xff1a; 1、由题目可知&#xff0c;他想让我们判断交换相邻字符位置后将二进制转为十进制后&#xff0c;能否整除2的次方。能整除即输出需要交换的次数&#xff0c;不能则输出-1。&#xff08;例&#xff1a;输入3和010这组数据就…

WebDAV之π-Disk派盘 +Polaris Office

推荐一款可以实现在Windows桌面PC,Mac,Android设备和iOS设备上同步的移动办公软件,还支持通过WebDAV添加葫芦儿派盘。Polaris Office是一款功能全面的办公自动化套件,软件集成了Word、Excel、幻灯片(PPT)、ODT等文档格式,并且可以完美兼容任何Microsoft Office,PDF,TXT或…

html web前端 登录,短信验证码登录

html web前端 登录&#xff0c;短信验证码登录 1&#xff0c;手机号码格式校验 2&#xff0c;按钮点击60秒倒计时&#xff0c;按钮限制点击 3&#xff0c;验证码/或密码长度校验&#xff08;被注释&#xff0c;公司发的验证码长度不一致&#xff0c;不一定是6位&#xff09; 4…

虹科培训 | 虹科携手PLCopen开展IEC 61131-3国际工程师培训

文章来源&#xff1a;虹科工业控制 阅读原文&#xff1a;https://mp.weixin.qq.com/s/MLYhBWiWx7qQSApx_3xhmA &#xff08;一&#xff09;课程背景 什么是IEC 61131-3&#xff1f; IEC 61131-3 是工业自动化行业唯一得到大量应用的组态编程语言国际标准&#xff1b;主导制定…

xcode15一直显示正在连接iOS17真机问题解决

前言 更新xcode15之后&#xff0c;出现了各种报错问题&#xff0c;可谓是一路打怪啊&#xff0c;解决一个报错问题又来一个。没想到到了最后还能出现一个一直显示正在连接iOS17真机的问题 一直显示正在连接iOS17真机的问题 问题截图如下&#xff1a; 解决方法 1. 打开De…

apple-app-site-association nginx

项目里面配置 applinks: 域名 eg: baidu.com 2. 创建 apple-app-site-association 文件&#xff0c;无json等后缀名 eg&#xff1a; appID 构成 teamId bundleId {"applinks": {"apps": [],"details": [{"appID": "2TEAM6D5.…

mysql查询最近7天 每天销售额 统计销售额

sql统计每一天的支出数目&#xff0c;支出金额&#xff0c;收入数目&#xff0c;收入金额 finance_type0&#xff1a;收入 finance_type1&#xff1a;支出 部分建表语句&#xff1a; CREATE TABLE finance (finance_id int(11) NOT NULL AUTO_INCREMENT COMMENT ID,finance_u…

Anaconda安装教程(Windows环境下)

下面的步骤是根据一个大佬的博文操作的&#xff0c;然后自己写了操作步骤和遇到的一些问题&#xff0c; 大佬博客在这&#xff1a; 【精选】Anaconda超详细安装教程&#xff08;Windows环境下&#xff09;_conda安装-CSDN博客 1、 Anaconda的下载&#xff1a; 一般两个选择&a…

05、Python -- 爬取ts文件格式视频思路

目录 第一步:爬取一段5秒视频找url代码结果第二步:下载整个视频的所有片段代码:结果:第三步:合成视频安装模块代码:结果简洁代码代码:结果:最终代码简洁前代码简洁后代码思路: 1、爬取视频,但是每次只能爬取一段5秒的视频。 2、一个视频有很多秒,所以需要爬取很多片…

用户需求深层挖掘的6大注意事项

在竞争日趋激烈的市场环境中&#xff0c;我们通过深入挖掘用户需求&#xff0c;更好地理解用户需求和痛点&#xff0c;从而有针对性的改进产品的使用体验&#xff0c;增强产品竞争力&#xff0c;尽可能地满足用户的需求和期望&#xff0c;提高用户满意度&#xff0c;避免产品开…

Pytorch:model.train()和model.eval()用法和区别,以及model.eval()和torch.no_grad()的区别

1 model.train() 和 model.eval()用法和区别 1.1 model.train() model.train()的作用是启用 Batch Normalization 和 Dropout。 如果模型中有BN层(Batch Normalization&#xff09;和Dropout&#xff0c;需要在训练时添加model.train()。model.train()是保证BN层能够用到每一…

【JavaEE重点知识归纳】第11节:认识异常

目录 一&#xff1a;异常的概念和体系结构 1.概念 2.体系结构 3.异常分类 二&#xff1a;异常的处理 1.防御式编程 2.异常的抛出 3.异常的捕获 4.异常的处理流程 三&#xff1a;自定义异常 一&#xff1a;异常的概念和体系结构 1.概念 &#xff08;1&#xff09;在…

vue3使用Element ui plus中MessageBox消息框+radio框配合使用

想要达到的效果 首先安装element ui plus 省略~~ 官网地址&#xff1a; https://element-plus.gitee.io/zh-CN/component/message-box.htmlhttps://element-plus.gitee.io/zh-CN/component/message-box.html 需要用到的 引入 import { h } from "vue"; import {E…

为什么需要山洪灾害监测预警系统?

在山洪高发地区&#xff0c;安装山洪灾害监测预警系统能够通过实时监测&#xff0c;预警山洪信息&#xff0c;对于保障我们的生命财产安全具有重要意义。 监测山洪不仅需要对山体进行监测&#xff0c;还要监测降雨量以及水位上升情况。山洪灾害监测预警系统是由GNSS监测站和水…

天锐绿盾加密软件——企业数据透明加密、防泄露系统

天锐绿盾是一种企业级数据透明加密、防泄密系统&#xff0c;旨在保护企业的核心数据&#xff0c;防止数据泄露和恶意攻击。它采用内核级透明加密技术&#xff0c;可以在不影响员工正常工作的前提下&#xff0c;对需要保护的数据进行加密操作。 PC访问地址&#xff1a; https:/…

选择工业交换机时,需要关注哪些方面的性能?

在工业自动化、能源、交通等领域的网络通信中&#xff0c;工业交换机是一种非常重要的网络设备。它的性能和可靠性直接影响到整个网络的稳定性和安全性。因此&#xff0c;在选择工业交换机时&#xff0c;我们需要关注以下几个方面的性能&#xff1a; 1. 抗干扰性能&#xff1a;…

如何将Linux上部署的5.7MySql数据库编码修改utf8(最新版)

如何将Linux&#xff08;服务器&#xff09;上部署的5.7MySql数据库编码修改utf8&#xff08;最新版&#xff09; 一、解决办法步骤1步骤2&#xff08;此处为问题描述吐槽&#xff0c;可以直接跳过该步骤到步骤三&#xff09;步骤3步骤4步骤5 二、结果 # 前言 提示&#xff1a…

【实战】Kubernetes安装持久化工具NFS-StorageClass

文章目录 前言技术积累存储类&#xff08;storage class&#xff09;什么是NFS什么是PV\PVC为什么要用NFS-StorageClass 安装NFS-StorageClass保证N8S集群正常投用安装NFS工具与客户端NFS安装常见错误安装NFS-StorageClass存储器 前言 前面的博文我们介绍了如何用kuberadmin的…

交流会|合同交付类业务的项目管理方法和实践分享

10月19日&#xff0c;由深圳市软件行业协会、易趋&#xff08;深圳蓝云软件&#xff09;、上海清晖、宁波银行深圳分行联合主办的第八期“项目管理技术与实践交流会议”在深圳成功举办。 本期沙龙邀请了易趋&#xff08;蓝云软件&#xff09;资深咨询顾问刘苗老师、协会特聘专…