20 动态内存管理

目录

一、为什么要有动态内存管理

二、malloc 和 free

(一)malloc

(二)free

三、calloc 和 realloc

(一)calloc

(二)realloc

四、常见的动态内存错误

(一)对NULL指针的解引用操作

(二)对动态开辟空间的越级访问

(三)对非动态开辟内存使用free释放

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

(五)对同一块动态内存多次释放

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

五、动态内存经典笔试题分析

(一)题目一

(二)题目二

(三)题目三

(四)题目四

六、柔性数组

(一)柔性数组的特点

(二)柔性数组的使用

(三)柔性数组的优势

        1、方便内存释放

        2、利于访问速度

七、总结C/C++中程序内存区域划分


一、为什么要有动态内存管理

        我们已经掌握的内存开辟方式有:

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

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

        • 空间开辟大小是固定的。

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

        但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的⽅式就不能满足了。 C语言引入了动态内存开辟,让程序员自己可以申请和释放空间,就比较灵活了。

二、malloc 和 free

(一)malloc

        malloc:

        第一个字母m是:memory,空间;

        后面的alloc:allocate:开辟;连起来就是开辟空间

        malloc 函数的一般形式如下:

void* malloc (size_t size);

        这个函数向内存申请一块大小为 size 字节的连续可用的空间,并返回指向这块空间的指针。

        ① 如果开辟成功,则返回一个指向开辟好空间的指针。

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

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

        ④ 参数的单位是字节,使用 sizeof 类型进行空间计算会很方便;如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

        ⑤ 为了后续空间的方便使用,一般不直接使用void* 指针来进行返回值的接收,而是直接使用想要设计的指针来接收,等号右边把 malloc 强制转化为对应的指针类型即可。

        ⑥ 申请的空间的地址是连续的,可以类似数组一样使用该空间,但与数组有区别:

                · malloc申请的空间是动态内存,可调节,而数组不可调节;

                · 开辟空间的位置不同:malloc开辟的空间在堆区,而数组是栈区。

(二)free

        C语言提供了另外一个函数 free,专门是用来做动态内存的释放和回收的,函数原型如下:

 void free (void* ptr);

        free 函数用来释放动态开辟的内存。

         • 参数是任意类型的指针,把指针指向的空间还给操作系统

         • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的,不能对固定内存进行释放操作。

         • 如果参数 ptr 是NULL指针,则函数什么事都不做。

        malloc 和 free 都声明在 stdlib.h 头文件中。

        例子:

#include<stdio.h>
#include<stdlib.h>int main()
{int num = 0;scanf("%d", &num);int* ptr = (int*)malloc(num * sizeof(int));if (ptr == NULL){perror("malloc:");return 0;}for (int i = 0; i < num; i++){*(ptr + i) = i;}free(ptr);ptr = NULL;return 0;
}

        注意:

        free之后,参数的指针还指向被释放的空间,但这块空间不属于当前程序了,所以该指针成为了野指针,要赋值 null !

        如果不释放的话,程序结束的时候也会被操作系统自动回收,但这样内存会被浪费。  

        malloc 和 free 最好成对使用!
 

三、calloc 和 realloc

(一)calloc

        C语⾔还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);

        • 函数的功能是为 num 个大小为 size 字节的元素开辟一块空间,并且把空间的每个字节初始化为0。

        • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0。

        使用演示如下:

int main()
{int* p = (int*)calloc(10, sizeof(int));if (p == NULL){perror("calloc:");return 0;}else{for (int i = 0; i < 10; i++){printf("%d ", *(p + i));}}free(p);p = NULL;return 0;
}

        输出结果为:

        所以如果我们对申请的内存空间的内容要求初始化,那么可以很⽅便的使用 calloc 函数来完成任务。

(二)realloc

        • realloc函数的出现让动态内存管理更加灵活。

        • 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。

        函数的原型如下:

void* realloc (void* ptr, size_t size);

        • ptr 是要调整的内存地址。

        • size 调整之后新大小,单位是字节

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

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

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

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

                ◦ 情况2:原有空间之后没有足够大的空间。

                如下:

                情况1:

                原有空间之后有足够大的空间,直接把扩容的内存拼接到旧内存处,使整体内存大小达到目标内存的大小,原来空间的数据不发生变化。

                情况2:

                原有空间之后没有足够大的空间时,扩展的方法是:在堆区找一块新的满足目标大小的空间,然后把旧数据拷贝到新空间,把旧空间自动释放,最后返回新空间的起始地址。

        注意:

        最好不要用旧的指针变量进行接收,因为开辟失败就会变成空指针,弄丢旧的数据;

        需要创建新的指针进行接收,同时要进行成功或失败的判断,然后再把新创建的指针赋给旧指针。

        如下演示:

#include<stdlib.h>int main()
{int* p = (int*)malloc(5*sizeof(int));if (NULL == p){perror("malloc:");return 1;}else{//业务代码}int* p2 = NULL;p2 = (int*)realloc(p, 40);if (NULL == p2){perror("realloc:");return 1;}else{p = p2;}//业务代码free(p); p = NULL;return 0;
}

四、常见的动态内存错误

(一)对NULL指针的解引用操作

void test1()
{int* p = (int*)malloc(INT_MAX / 4);*p = 20;free(p);
}

        解析:如果p的值是NULL,就会有问题;且最后不对p进行赋NULL操作。

(二)对动态开辟空间的越级访问

void test2()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}for (i = 0; i <= 10; i++){*(p + i) = i;}free(p);p = NULL;
}

        解析:当i是10的时候越界访问。

(三)对非动态开辟内存使用free释放

void test3()
{int i = 0;int* p = &i;free(p);
}

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

void test4()
{int* p = (int*)malloc(100);p++;free(p);
}

        解析:p不再指向动态内存的起始位置。

(五)对同一块动态内存多次释放

void test()
{int* p = (int*)malloc(100);free(p);free(p);
}

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

void test6()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();return 0;
}

        解析:

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

        切记:动态开辟的空间一定要释放,并且正确释放。

五、动态内存经典笔试题分析

(一)题目一

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

        解析:实参传给形参,形参是实参的一份拷贝,GetMemory函数创建出一个新的指针p来接收创建出来的动态内存,这样p指针可以找到该动态内存,但是str指针找不到;

        当该函数运行结束,str仍然是空指针,就会造成对空指针的解引用操作,程序就会崩溃,且并没有对指针p进行free操作,造成内存泄漏。 

        正确修改:进行地址传递给GetMemory函数,后面 printf 完 str 之后,进行free与赋NULL操作。

(二)题目二

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函数中创建的数组是在栈上创建的,出了GetMemory函数就会销毁,所以该函数会返回一个野指针。

(三)题目三

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;
}

        解析:能正常打印出hello,但是没有对不再使用的动态开辟出来的空间进行释放,导致内存泄漏。

        正确修改是进行free与赋NULL操作。

(四)题目四

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;
}

        解析:第一次free操作之后str指向的空间还给了操作系统,无法继续使用,但str仍然指向该空间,为野指针,若对该指针继续进行使用,就形成了非法访问

        正确修改应该是free操作之后要赋空值NULL给str。

六、柔性数组

        C99 中,结构体中最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

struct st_type
{int i;int a[0];//柔性数组
};

        有些编译器会报错无法编译可以改成:

struct st_type
{int i;int a[];//柔性数组
};

(一)柔性数组的特点

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

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

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

        例如:

typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;
int main()
{printf("%d\n", sizeof(type_a));return 0;
}

        结果为:

(二)柔性数组的使用

        使用例如下:

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

(三)柔性数组的优势

        1、方便内存释放

        如果我们的代码是在一个给别人用的函数中,你在里面做了⼆次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返 回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。

        2、利于访问速度

        连续的内存有益于提高访问速度,也有益于减少内存碎片。

七、总结C/C++中程序内存区域划分

        内存中的分区如下所示:

        C/C++程序内存分配的几个区域:
        ①  内核空间:专门留给操作系统内核的。
        ② 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时,这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等
         ③ 堆区(heap):⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
        ④ 数据段(静态区static):存放全局变量、静态数据。程序结束后由系统释放。
        ⑥ 代码段:存放函数体(类成员函数和全局函数)和只读常量的二进制代码,代码段的数据是不能篡改的

        以上内容仅供分享,若有错误,请多指正

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

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

相关文章

前端本地代理配置方式

Whistle 介绍 Whistle 是一个基于 Node.js 的跨平台 Web 调试工具。允许捕获、查看和修改 HTTP/HTTPS 网络请求。通过使用 Whistle&#xff0c;可以轻松地进行接口代理、抓包、模拟数据、修改请求和响应等操作&#xff0c;以便在前端开发中调试网络请求。 Proxy SwitchyOmega…

133-横向移动域控提权NetLogonADCSPACKDC永恒之蓝

除了前面讲到的口令密码进行横向移动&#xff0c;还存在使用系统漏洞进行的横向移动的方式&#xff0c;本节课就是讲一些域内系统的漏洞&#xff0c;主要是域控提权的一些漏洞 1、横向移动-系统漏洞-CVE-2017-0146&#xff08;ms17-010&#xff0c;永恒之蓝&#xff09; 2、横…

Java之迭代器的使用

Java之迭代器的使用 摘要基础知识List迭代器Map迭代器 摘要 本博客主要讲解容器的迭代器的使用&#xff0c;包括List、Set和Map等容器 基础知识 这是类的继承关系图 迭代器的原理(一开始迭代器并不指向任何有效元素)&#xff1a; List迭代器 public class TestIterator …

World of Warcraft [CLASSIC] the Eye of Eternity [EOE] P1-P2

World of Warcraft [CLASSIC] the Eye of Eternity [EOE] 永恒之眼&#xff08;蓝龙&#xff09; 第一阶段 第二阶段 第三阶段 载具1-6技能介绍 World of Warcraft [CLASSIC] the Eye of Eternity [EOE]_永恒之眼 eoe-CSDN博客 永恒之眼怎么出副本呢&#xff0c;战斗结束&am…

【Java】/* 链式队列 和 循环队列 - 底层实现 */

一、链式队列 1. 使用双向链表实现队列&#xff0c;可以采用尾入&#xff0c;头出 也可以采用 头入、尾出 (LinkedList采用尾入、头出) 2. 下面代码实现的是尾入、头出&#xff1a; package bageight;/*** Created with IntelliJ IDEA.* Description:* User: tangyuxiu* Date: …

windows安装android studio

下载 https://developer.android.google.cn/studio?hlzh-cn 安装 打开cmd输入如下命令 android-studio-2024.1.1.12-windows.exe /NCRC 注意 运行命令后可能还报错&#xff0c;但是会出现弹窗 如果还是报错可以选择zip 运行 不设置代理 等待下载即可&#xff0c;…

Linux云计算 |【第二阶段】SECURITY-DAY3

主要内容&#xff1a; Prometheus监控服务器、Prometheus被监控端、Grafana监控可视化 补充&#xff1a;Zabbix监控软件不自带LNMP和DB数据库&#xff0c;需要自行手动安装配置&#xff1b;Prometheus监控软件自带WEB页面和DB数据库&#xff1b;Prometheus数据库为时序数据库&…

Android 14适配

最近刚刚做了Android 14的适配&#xff08;即targetSdkVersion 升级到 34 &#xff09;&#xff0c;通过此博客整理下相关注意点。 前台服务类型 当targetSdkVersion > 34 &#xff0c;应用内的前台服务&#xff08;Foreground Service&#xff09;需要指定至少一种前台服务…

k8s - Secret实践练习

参考文档&#xff1a;https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/ 这个和ConfigMap很相似&#xff0c;这里选两个做下测试&#xff0c;就不过多赘述了 简介 Secret 类似于 ConfigMap 但专门用于保存机密数据。 Secret 是一种包含少量敏感信息例如密码…

qt creator自动运行单元测试

qt creator自动运行单元测试 工具-选项-Testing-General&#xff0c;找到Automatically run&#xff0c;选项卡选择All。

[C语言]-基础知识点梳理-编译、链接、预处理

前言 各位师傅大家好&#xff0c;我是qmx_07,今天来给大家讲解以下程序运行会经历哪些事情 翻译环境和运⾏环境 在ANSIC的任何⼀种实现中&#xff0c;存在两个不同的环境 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执⾏的机器指令&#xff08;⼆进制指令&a…

Linux下opencv报错 undefined reference to cv::imread cv::Mat

如果你是和libtorch一起使用&#xff0c;那么请你继续&#xff0c;否则该篇文章不适合你。 正文 在https://pytorch.org/下 下载的时候要选择Cxx11 ABI版 随后正常配置就可以了

Langchain-Chatchat

模型能力定制 微调 智能设备 微调 响应有要求 微调 动态数据 RAG 幻觉 RAG 可解释性 RAG 成本 RAG 依赖生成能力 微调 微调需要几万、几十万条好的数据&#xff0c;否者白调&#xff0c;所以是否需要微调&#xff0c;需要视情况而定。 RAG的落地&#xff0c;可以使用 https:/…

wangeditor编辑器自定义按钮和节点,上传word转换html,文本替换

vue3ts 需求&#xff1a;在编辑器插入图片和视频时下方会有一个输入框填写描述&#xff0c;上传word功能 wangeditor文档wangEditor开源 Web 富文本编辑器&#xff0c;开箱即用&#xff0c;配置简单https://www.wangeditor.com/ 安装&#xff1a;npm install wangeditor/edit…

Datawhale X 李宏毅苹果书 AI夏令营-深度学习入门班-task1

机器学习就是去拟合一种函数&#xff0c;它可能在高维上&#xff0c;十分抽象&#xff0c;但是却可以有丰富的语义含义&#xff0c;从而完成一系列任务 回归任务是预测一个准确的值&#xff0c;例如拟合一条直线的时候&#xff0c;我们希望每一个点的值都能对应上 分类任务则…

java多线程(六)关键字Volatile可见性、有序性以及单个变量的原子性

volatile关键字 作用 volatile 是 Java 虚拟机提供的轻量级的同步机制&#xff0c;主要用来确保变量被线程安全地读取和写入。 当一个变量定义为 volatile 后&#xff0c;它具备以下特性&#xff1a; 可见性&#xff1a;确保不同线程对这个变量操作的可见性&#xff0c;即一…

深入学习SQL优化的第三天

目录 聚合函数 排序和分组 聚合函数 1251. 平均售价 表&#xff1a;Prices------------------------ | Column Name | Type | ------------------------ | product_id | int | | start_date | date | | end_date | date | | price | int …

K8S - Java微服务配置 - 使用ConfigMap配置redis

参考文档&#xff1a;https://v1-27.docs.kubernetes.io/zh-cn/docs/tutorials/configuration/configure-redis-using-configmap/ cat <<EOF >./example-redis-config.yaml apiVersion: v1 kind: ConfigMap metadata:name: example-redis-config data:redis-config: …

“解决Windows电脑无法投影到其他屏幕的问题:尝试更新驱动程序或更换视频卡“

目录 背景: 解决方法1: 解决方法2: 什么是驱动程序&#xff1a; 背景: 今天在日常的工作中&#xff0c; 我想将笔记本分屏到另一个显示屏&#xff0c;我这电脑Windows10系统&#xff0c;当我按下Windows键P键&#xff0c;屏幕信息上提示我"你的电脑不能投影到其他屏幕…

C++:继承(protected、隐藏、不能被继承的类、)

目录 继承的概念 继承的使用 继承方式 protected 继承类模板 赋值兼容转换 隐藏 子类的默认成员函数 构造函数 拷贝构造函数 赋值重载函数 析构函数 不能被继承的类 方法1&#xff1a;父类的构造函数私有 方法2&#xff1a;final 继承与友元 继承与静态成员 …